Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playwright tests #2254

Merged
merged 17 commits into from
Aug 7, 2023
11 changes: 8 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
run: npm run test

end2end-tests:
runs-on: ubuntu-latest
runs-on: windows-latest

steps:
- name: Checkout code
Expand All @@ -32,6 +32,12 @@ jobs:
- name: Install
run: npm install

- name: Compile
run: npx tsc -p tests\tsconfig.json

- name: Install Playwright Browsers
run: npx playwright install --with-deps

- name: Build static data
run: npm run build-mock-static-data

Expand All @@ -43,5 +49,4 @@ jobs:
shell: pwsh

- name: Run tests
run: npm run test-e2e

run: npx playwright test /tests --workers 1
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ node_modules/
src/config.design.json
src/config.publish.json
src/config.runtime.json

test-results/
tsconfig.tsbuildinfo
551 changes: 280 additions & 271 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"build-publisher": "webpack --config webpack.publisher.js",
"build-runtime": "webpack --config webpack.runtime.js",
"build-function": "webpack --config webpack.function.js",
"test-e2e": "node node_modules/mocha/bin/_mocha -r mocha.js tests/e2e/**/*.spec.ts --timeout 3000000",
"test": "node node_modules/mocha/bin/_mocha -r mocha.js src/**/*.spec.ts",
"deploy-function": "npm run build-function && cd dist/function && func azure functionapp publish < function app name >",
"publish": "webpack --config webpack.publisher.js && node dist/publisher/index.js && npm run serve-website",
Expand Down Expand Up @@ -65,11 +64,12 @@
"ts-loader": "^9.4.3",
"ts-node": "10.9.1",
"typescript": "^4.9.5",
"url-loader": "^4.1.1",
"webpack": "5.88.0",
"webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1",
"webpack-merge": "5.9.0"
"webpack-merge": "5.9.0",
"playwright": "1.35.1",
"@playwright/test": "1.35.1"
},
"dependencies": {
"@azure/api-management-custom-widgets-scaffolder": "^1.0.0-beta.2",
Expand Down
9 changes: 9 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testIgnore: 'playwright/*',
//timeout: 30_000,
use: {
video: 'retain-on-failure'
}
});
1 change: 1 addition & 0 deletions src/config.validate.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"environment": "validation",
"isLocalRun": true,
"root": "http://localhost:8080",
"urls": {
"home": "/",
Expand Down
2 changes: 1 addition & 1 deletion src/services/usersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export class UsersService {
*/
public async ensureSignedIn(): Promise<string> {
const userId = await this.getCurrentUserId();

f-alizada marked this conversation as resolved.
Show resolved Hide resolved
if (!userId) {
this.navigateToSignin();
return; // intentionally exiting without resolving the promise.
Expand Down
10 changes: 0 additions & 10 deletions tests/constants.ts

This file was deleted.

17 changes: 15 additions & 2 deletions tests/e2e/maps/apis.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { Page } from "puppeteer";
import { Page } from "playwright";

export class ApisWidget {
constructor(private readonly page: Page) { }

public async apis(): Promise<void> {
public async waitRuntimeInit(): Promise<void> {
await this.page.waitForSelector("api-list.block");
await this.page.waitForSelector("api-list div.table div.table-body div.table-row");
}

public async getApiByName(apiName: string): Promise<object | null> {
const apis = await this.page.$$('api-list div.table div.table-body div.table-row a');

for (let i = 0; i < apis.length; i++) {
const productNameHtml = await (await apis[i].getProperty('innerText')).jsonValue();
if (productNameHtml == apiName){
return apis[i];
}
}
return null;
}

public async getApisCount(): Promise<number | undefined> {
return await this.page.evaluate(() =>
document.querySelector("api-list div.table div.table-body div.table-row")?.parentElement?.childElementCount
Expand Down
29 changes: 27 additions & 2 deletions tests/e2e/maps/products.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Page } from "puppeteer";
import { Page } from "playwright";

export class ProductseWidget {
constructor(private readonly page: Page) { }

public async products(): Promise<void> {
public async waitRuntimeInit(): Promise<void> {
await this.page.waitForSelector("product-list-runtime.block");
await this.page.waitForSelector("product-list-runtime div.table div.table-body div.table-row");
}

Expand All @@ -12,4 +13,28 @@ export class ProductseWidget {
document.querySelector("product-list-runtime div.table div.table-body div.table-row")?.parentElement?.childElementCount
);
}

public async getProductByName(productName: string): Promise<object | null> {
const products = await this.page.$$('product-list-runtime div.table div.table-body div.table-row a');

for (let i = 0; i < products.length; i++) {
const productNameHtml = await (await products[i].getProperty('innerText')).jsonValue();
if (productNameHtml == productName){
return products[i];
}
}
return null;
}

public async goToProductPage(baseUrl, productId: string): Promise<void>{
await this.page.goto(`${baseUrl}/product#product=${productId}`);
}

public async subscribeToProduct(baseUrl, productId: string, subscriptionName: string): Promise<void> {
await this.goToProductPage(baseUrl, productId);
await this.page.waitForSelector("product-subscribe-runtime form button");
await this.page.type("product-subscribe-runtime form input", subscriptionName);
await this.page.click("product-subscribe-runtime form button");
await this.page.waitForNavigation({ waitUntil: "domcontentloaded" });
}
}
81 changes: 76 additions & 5 deletions tests/e2e/maps/profile.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,86 @@
import { Page } from "puppeteer";
import { Locator, Page } from "playwright";

export class ProfileWidget {
constructor(private readonly page: Page) { }

public async profile(): Promise<void> {
public async waitRuntimeInit(): Promise<void> {
await this.page.waitForSelector("profile-runtime .row");
await this.page.waitForSelector("subscriptions-runtime .table-row");
}

public async getUserEmail(): Promise<string | undefined | null> {
await this.page.waitForSelector("[data-bind='text: user().email']");
return await this.page.evaluate(() =>document.querySelector("[data-bind='text: user().email']")?.textContent);
public async getUserEmailLocator(): Promise<Locator | null> {
return this.page.locator("profile-runtime [data-bind='text: user().email']").first();
}

public async getUserEmail(): Promise<string | null> {
return (await this.getUserEmailLocator()).innerText();
}

public async getUserFirstNameLocator(): Promise<Locator | null> {
return this.page.locator("profile-runtime [data-bind='text: user().firstName']").first();
}

public async getUserFirstName(): Promise<string | null> {
return (await this.getUserFirstNameLocator()).innerText();
}

public async getUserLastNameLocator(): Promise<Locator | null> {
return this.page.locator("profile-runtime [data-bind='text: user().lastName']").first();
}

public async getUserLastName(): Promise<string | null> {
return (await this.getUserLastNameLocator()).innerText();
}

public async getUserRegistrationDataLocator(): Promise<Locator | null> {
return this.page.locator("profile-runtime [data-bind='text: registrationDate']").first();
}

public async getUserRegistrationDate(): Promise<string | null> {
return (await this.getUserRegistrationDataLocator()).innerText();
}

public async getSubscriptionRow(subscriptionName: string): Promise<Locator | undefined> {
return this.page.locator("subscriptions-runtime div.table div.table-body div.table-row", { has: this.page.locator("div.row span[data-bind='text: model.name']").filter({ hasText: subscriptionName })});
}

public async getSubscriptioPrimarynKey(subscriptionName: string): Promise<string | undefined> {
var subscriptionRow = await this.getSubscriptionRow(subscriptionName);
const primaryKeyElement = subscriptionRow.locator('code[data-bind="text: primaryKey"]').first();
return await primaryKeyElement.textContent();
}

public async getSubscriptioSecondarynKey(subscriptionName: string): Promise<string | undefined> {
var subscriptionRow = await this.getSubscriptionRow(subscriptionName);
const primaryKeyElement = subscriptionRow.locator('code[data-bind="text: secondaryKey"]').first();
return await primaryKeyElement.textContent();
}

public async togglePrimarySubscriptionKey(subscriptionName: string): Promise<void> {
var subscriptionRow = await this.getSubscriptionRow(subscriptionName);
await subscriptionRow.locator("a.btn-link[aria-label='Show primary key']", { hasText: "Show" }).click();
}

public async toggleSecondarySubscriptionKey(subscriptionName: string): Promise<void> {
var subscriptionRow = await this.getSubscriptionRow(subscriptionName);
await subscriptionRow.locator("a.btn-link[aria-label='Show Secondary key']", { hasText: "Show" }).click();
}

public async getListOfLocatorsToHide(): Promise<Locator[] | undefined> {
const primaryKeyElements = await this.page.locator('code[data-bind="text: primaryKey"]').all();
const secondaryKeyElements = await this.page.locator('code[data-bind="text: secondaryKey"]').all();
const productNames = this.page.locator('span[data-bind="text: model.productName"]');
const subscriptionNames = this.page.locator('span[data-bind="text: model.name"]');
const subscriptionStartDates = this.page.locator('span[data-bind="text: $parent.timeToString(model.startDate)"]');
return primaryKeyElements.concat(secondaryKeyElements).concat(productNames).concat(await this.getUserProfileData()).concat(subscriptionNames).concat(subscriptionStartDates);
}

public async getUserProfileData(): Promise<Locator[] | undefined> {
return [
await this.getUserEmailLocator(),
await this.getUserFirstNameLocator(),
await this.getUserLastNameLocator(),
await this.getUserRegistrationDataLocator()
];
}
}
13 changes: 8 additions & 5 deletions tests/e2e/maps/signin-basic.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Page } from "puppeteer";
import { Page } from "playwright";
import { User } from "../../mocks/collection/user";

export class SignInBasicWidget {
constructor(private readonly page: Page) { }
constructor(private readonly page: Page, private readonly configuration: object) { }

public async signInWithBasic(): Promise<void> {
await this.page.type("#email", "foo@bar.com");
await this.page.type("#password", "password");
public async signInWithBasic(userInfo: User): Promise<void> {
await this.page.goto(this.configuration['urls']['signin']);

await this.page.type("#email", userInfo.email);
await this.page.type("#password", userInfo.password);
await this.page.click("#signin");
await this.page.waitForNavigation({ waitUntil: "domcontentloaded" });
}
Expand Down
21 changes: 14 additions & 7 deletions tests/e2e/maps/signup-basic.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { Page } from "puppeteer";
import { Page } from "playwright";
import { User } from "../../mocks/collection/user";

export class SignupBasicWidget {
constructor(private readonly page: Page) { }

public async signUpWithBasic(): Promise<void> {
await this.page.type("#email", "foo@bar.com");
await this.page.type("#password", "password");
await this.page.type("#confirmPassword", "password");
await this.page.type("#firstName", "Foo");
await this.page.type("#lastName", "Bar");
public async signUpWithBasic(user: User): Promise<void> {
await this.page.type("#email", user.email);
await this.page.type("#password", user.password);
await this.page.type("#confirmPassword", user.password);
await this.page.type("#firstName", user.firstName);
await this.page.type("#lastName", user.lastName);

var captchaTextBox = await this.page.evaluate(() => document.getElementById("captchaValue"));
if (captchaTextBox) {
console.log("Captcha is enabled and should be passed with the sign up request.");
}

await this.page.click("#signup");
}

Expand Down
82 changes: 82 additions & 0 deletions tests/e2e/playwright-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { test as base } from '@playwright/test';
import { Utils } from '../utils';
import { ApiService } from '../services/apiService';
import { UserService } from '../services/userService';
import { ProductService } from '../services/productService';
import { ITestRunner } from '../services/ITestRunner';
import { TestRunnerMock } from '../services/testRunnerMock';
import { TestRunner } from '../services/testRunner';

let configurationTest = base.extend<{}, { configuration: Object, cleanUp: Array<Function>, apiService: ApiService, userService: UserService, productService: ProductService, testRunner: ITestRunner }>({
configuration: [async ({}, use) => {
let configuration = {};
configuration = await Utils.getConfigAsync();
await use(configuration);
}, { scope: 'worker' }],

testRunner: [async ({}, use) => {
let testRunner: ITestRunner;
if (!(await Utils.IsLocalEnv())){
testRunner = new TestRunner();
}else{
testRunner = new TestRunnerMock();
}
await use(testRunner);
}, { scope: 'worker' }],

apiService: [async ({}, use) => {
let apiService = new ApiService();
await use(apiService);
}, { scope: 'worker' }],

productService: [async ({}, use) => {
let productService = new ProductService();
await use(productService);
}, { scope: 'worker' }],

userService: [async ({}, use) => {
let userService = new UserService();
await use(userService);
}, { scope: 'worker' }],

cleanUp: [async ({}, use) => {
let cleanUp: Array<Function> = [];
await use(cleanUp);
}, { scope: 'worker' }],

page: async ({ page }, use) => {
page.on("console", (message) => {
if(message.type() === "error"){
console.error(message.text());
}
});
await use(page);
},
});


export const test = configurationTest.extend({
mockedData: async ({ }, use, testInfo) => {
let testTitle = `${testInfo.titlePath[1]}-${testInfo.titlePath[2]}`;
var dataToUse = Utils.getTestData(testTitle);
let mockedData = {};
mockedData["data"] = dataToUse;
mockedData["testName"] = testTitle;
await use(mockedData);
},
});

test.beforeEach(async ( { cleanUp } ) => {
console.log("initializing clean up functions");
cleanUp = [];
});

test.afterEach(async ( { cleanUp } ) => {
console.log("amount of clean up functions: " + cleanUp.length);
for (const cleanUpFunction of cleanUp) {
await cleanUpFunction();
}
});


export { expect } from '@playwright/test';
Loading
Loading