Skip to content

Commit

Permalink
Playwright tests (#2254)
Browse files Browse the repository at this point in the history
* Add Playwright tests with image comparison for profile page
---------

Co-authored-by: Farhad Alizada <falizada@microsoft.com>
  • Loading branch information
f-alizada and Farhad Alizada authored Aug 7, 2023
1 parent d395754 commit e350f81
Show file tree
Hide file tree
Showing 47 changed files with 1,552 additions and 895 deletions.
19 changes: 16 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,16 +32,29 @@ 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

- name: Start mock server
run: npm run serve-website &
shell: bash

- name: Wait for the server
run: ./.github/scripts/wait-for-server.ps1 -HostName "http://localhost:8080"
shell: pwsh

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

run: npx playwright test tests/ --workers 1

- uses: actions/upload-artifact@v3
if: failure()
with:
name: playwright-report
path: test-results/
retention-days: 30
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.

8 changes: 3 additions & 5 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 All @@ -38,7 +37,6 @@
"@types/mime": "^3.0.1",
"@types/mocha": "10.0.1",
"@types/node": "^20.3.1",
"@types/puppeteer": "5.4.7",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"autoprefixer": "^10.4.14",
Expand All @@ -54,7 +52,6 @@
"mocha": "^10.2.0",
"path": "^0.12.7",
"postcss-loader": "^7.3.3",
"puppeteer": "19.7.5",
"querystring-es3": "^0.2.1",
"raw-loader": "^4.0.2",
"sass": "^1.63.6",
Expand All @@ -65,11 +62,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
8 changes: 8 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
retries: 2,
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
1 change: 0 additions & 1 deletion src/services/usersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ export class UsersService {
*/
public async ensureSignedIn(): Promise<string> {
const userId = await this.getCurrentUserId();

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.

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

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

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

}

public async getApiByName(apiName: string): Promise<string | null> {
return await this.page.locator('api-list div.table div.table-body div.table-row a').filter({ hasText: apiName }).first().innerText();
}

public async getApisCount(): Promise<number | undefined> {
Expand Down
13 changes: 13 additions & 0 deletions tests/e2e/maps/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Locator, Page } from "playwright";

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

public async waitRuntimeInit(): Promise<void> {
await this.getWelcomeMessageLocator().waitFor();
}

public getWelcomeMessageLocator(): Locator {
return this.page.locator("h1 span").filter({ hasText: "Welcome to Contoso!" });
}
}
23 changes: 16 additions & 7 deletions tests/e2e/maps/products.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { Page } from "puppeteer";
import { Page } from "playwright";

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

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

public async getProductsCount(): Promise<number | undefined> {
return await this.page.evaluate(() =>
document.querySelector("product-list-runtime div.table div.table-body div.table-row")?.parentElement?.childElementCount
);
public async getProductByName(productName: string): Promise<string | null> {
return await this.page.locator('product-list-runtime div.table div.table-body div.table-row a').filter({ hasText: productName }).first().innerText();
}

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

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");
}
}
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 getUserEmailLocator(): Locator {
return this.page.locator("profile-runtime [data-bind='text: user().email']").first();
}

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

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

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

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

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

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

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

public getSubscriptionRow(subscriptionName: string): Locator {
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 | null> {
var subscriptionRow = this.getSubscriptionRow(subscriptionName);
const primaryKeyElement = subscriptionRow.locator('code[data-bind="text: primaryKey"]').first();
return await primaryKeyElement.textContent();
}

public async getSubscriptioSecondarynKey(subscriptionName: string): Promise<string | null> {
var subscriptionRow = 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 = 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 = 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(this.getUserProfileData()).concat(subscriptionNames).concat(subscriptionStartDates);
}

public getUserProfileData(): Locator[] {
return [
this.getUserEmailLocator(),
this.getUserFirstNameLocator(),
this.getUserLastNameLocator(),
this.getUserRegistrationDataLocator()
];
}
}
14 changes: 8 additions & 6 deletions tests/e2e/maps/signin-basic.ts
Original file line number Diff line number Diff line change
@@ -1,12 +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'], { waitUntil: 'domcontentloaded' });

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" });
}
}
24 changes: 0 additions & 24 deletions tests/e2e/maps/signin-social.ts

This file was deleted.

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
Loading

0 comments on commit e350f81

Please sign in to comment.