Skip to content

Commit

Permalink
Fix playwright flaky tests
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
  • Loading branch information
t3chguy committed Jan 10, 2025
1 parent effef7e commit ad0b86c
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 29 deletions.
3 changes: 3 additions & 0 deletions docs/playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ test.use({
```

The appropriate homeserver will be launched by the Playwright worker and reused for all tests which match the worker configuration.
Due to homeservers being reused between tests, please use unique names for any rooms put into the room directory as
they may be visible from other tests, the suggested approach is to use `testInfo.testId` within the name or lodash's uniqueId.
We remove public rooms from the room directory between tests but deleting users doesn't have a homeserver agnostic solution.
The logs from testcontainers will be attached to any reports output from Playwright.

## Writing Tests
Expand Down
53 changes: 33 additions & 20 deletions playwright/e2e/forgot-password/forgot-password.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/

import { expect, test } from "../../element-web-test";
import { expect, test as base } from "../../element-web-test";
import { selectHomeserver } from "../utils";
import { emailHomeserver } from "../../plugins/homeserver/synapse/emailHomeserver.ts";
import { isDendrite } from "../../plugins/homeserver/dendrite";
import { Credentials } from "../../plugins/homeserver";

const username = "user1234";
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
const password = "oETo7MPf0o";
const email = "user@nowhere.dummy";

const test = base.extend<{ credentials: Pick<Credentials, "username" | "password"> }>({
// eslint-disable-next-line no-empty-pattern
credentials: async ({}, use, testInfo) => {
await use({
username: `user_${testInfo.testId}`,
// this has to be password-like enough to please zxcvbn. Needless to say it's just from pwgen.
password: "oETo7MPf0o",
});
},
});

test.use(emailHomeserver);
test.use({
config: {
Expand Down Expand Up @@ -45,31 +54,35 @@ test.describe("Forgot Password", () => {
await expect(page.getByRole("main")).toMatchScreenshot("forgot-password.png");
});

test("renders email verification dialog properly", { tag: "@screenshot" }, async ({ page, homeserver }) => {
const user = await homeserver.registerUser(username, password);
test(
"renders email verification dialog properly",
{ tag: "@screenshot" },
async ({ page, homeserver, credentials }) => {
const user = await homeserver.registerUser(credentials.username, credentials.password);

await homeserver.setThreepid(user.userId, "email", email);
await homeserver.setThreepid(user.userId, "email", email);

await page.goto("/");
await page.goto("/");

await page.getByRole("link", { name: "Sign in" }).click();
await selectHomeserver(page, homeserver.baseUrl);
await page.getByRole("link", { name: "Sign in" }).click();
await selectHomeserver(page, homeserver.baseUrl);

await page.getByRole("button", { name: "Forgot password?" }).click();
await page.getByRole("button", { name: "Forgot password?" }).click();

await page.getByRole("textbox", { name: "Email address" }).fill(email);
await page.getByRole("textbox", { name: "Email address" }).fill(email);

await page.getByRole("button", { name: "Send email" }).click();
await page.getByRole("button", { name: "Send email" }).click();

await page.getByRole("button", { name: "Next" }).click();
await page.getByRole("button", { name: "Next" }).click();

await page.getByRole("textbox", { name: "New Password", exact: true }).fill(password);
await page.getByRole("textbox", { name: "Confirm new password", exact: true }).fill(password);
await page.getByRole("textbox", { name: "New Password", exact: true }).fill(credentials.password);
await page.getByRole("textbox", { name: "Confirm new password", exact: true }).fill(credentials.password);

await page.getByRole("button", { name: "Reset password" }).click();
await page.getByRole("button", { name: "Reset password" }).click();

await expect(page.getByRole("button", { name: "Resend" })).toBeInViewport();
await expect(page.getByRole("button", { name: "Resend" })).toBeInViewport();

await expect(page.locator(".mx_Dialog")).toMatchScreenshot("forgot-password-verify-email.png");
});
await expect(page.locator(".mx_Dialog")).toMatchScreenshot("forgot-password-verify-email.png");
},
);
});
6 changes: 6 additions & 0 deletions playwright/e2e/login/login-consent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ async function login(page: Page, homeserver: HomeserverInstance, credentials: Cr
await page.getByRole("button", { name: "Sign in" }).click();
}

// This test suite uses the same userId for all tests in the suite
// due to DEVICE_SIGNING_KEYS_BODY being specific to that userId,
// so we restart the Synapse container to make it forget everything.
test.use(consentHomeserver);
test.use({
config: {
Expand All @@ -97,6 +100,9 @@ test.use({
...credentials,
displayName,
});

// Restart the homeserver to wipe its in-memory db so we can reuse the same user ID without cross-signing prompts
await homeserver.restart();
},
});

Expand Down
17 changes: 14 additions & 3 deletions playwright/e2e/oidc/oidc-native.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,30 @@ test.describe("OIDC Native", { tag: ["@no-firefox", "@no-webkit"] }, () => {
test.skip(isDendrite, "does not yet support MAS");
test.slow(); // trace recording takes a while here

test("can register the oauth2 client and an account", async ({ context, page, homeserver, mailhogClient, mas }) => {
test("can register the oauth2 client and an account", async ({
context,
page,
homeserver,
mailhogClient,
mas,
}, testInfo) => {
await page.clock.install();

const tokenUri = `${mas.baseUrl}/oauth2/token`;
const tokenApiPromise = page.waitForRequest(

Check failure on line 30 in playwright/e2e/oidc/oidc-native.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 6/6

[Chrome] › oidc/oidc-native.spec.ts:20:9 › OIDC Native › can register the oauth2 client and an account @no-firefox @no-webkit

1) [Chrome] › oidc/oidc-native.spec.ts:20:9 › OIDC Native › can register the oauth2 client and an account @no-firefox @no-webkit Error: page.waitForRequest: Test ended. 28 | 29 | const tokenUri = `${mas.baseUrl}/oauth2/token`; > 30 | const tokenApiPromise = page.waitForRequest( | ^ 31 | (request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code", 32 | ); 33 | at /home/runner/work/element-web/element-web/playwright/e2e/oidc/oidc-native.spec.ts:30:38

Check failure on line 30 in playwright/e2e/oidc/oidc-native.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 6/6

[Chrome] › oidc/oidc-native.spec.ts:20:9 › OIDC Native › can register the oauth2 client and an account @no-firefox @no-webkit

1) [Chrome] › oidc/oidc-native.spec.ts:20:9 › OIDC Native › can register the oauth2 client and an account @no-firefox @no-webkit Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: page.waitForRequest: Test ended. 28 | 29 | const tokenUri = `${mas.baseUrl}/oauth2/token`; > 30 | const tokenApiPromise = page.waitForRequest( | ^ 31 | (request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code", 32 | ); 33 | at /home/runner/work/element-web/element-web/playwright/e2e/oidc/oidc-native.spec.ts:30:38

Check failure on line 30 in playwright/e2e/oidc/oidc-native.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 6/6

[Chrome] › oidc/oidc-native.spec.ts:20:9 › OIDC Native › can register the oauth2 client and an account @no-firefox @no-webkit

1) [Chrome] › oidc/oidc-native.spec.ts:20:9 › OIDC Native › can register the oauth2 client and an account @no-firefox @no-webkit Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: page.waitForRequest: Test ended. 28 | 29 | const tokenUri = `${mas.baseUrl}/oauth2/token`; > 30 | const tokenApiPromise = page.waitForRequest( | ^ 31 | (request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code", 32 | ); 33 | at /home/runner/work/element-web/element-web/playwright/e2e/oidc/oidc-native.spec.ts:30:38
(request) => request.url() === tokenUri && request.postDataJSON()["grant_type"] === "authorization_code",
);

await page.goto("/#/login");
await page.getByRole("button", { name: "Continue" }).click();
await registerAccountMas(page, mailhogClient, "alice", "alice@email.com", "Pa$sW0rD!");

const userId = `alice_${testInfo.testId}`;
await registerAccountMas(page, mailhogClient, userId, "alice@email.com", "Pa$sW0rD!");

// Eventually, we should end up at the home screen.
await expect(page).toHaveURL(/\/#\/home$/, { timeout: 10000 });
await expect(page.getByRole("heading", { name: "Welcome alice", exact: true })).toBeVisible();
await expect(page.getByRole("heading", { name: `Welcome ${userId}`, exact: true })).toBeVisible();
await page.clock.runFor(20000); // run the timer so we see the token request

const tokenApiRequest = await tokenApiPromise;
expect(tokenApiRequest.postDataJSON()["grant_type"]).toBe("authorization_code");
Expand Down
4 changes: 2 additions & 2 deletions playwright/e2e/one-to-one-chat/one-to-one-chat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const test = base.extend<{
test.describe("1:1 chat room", () => {
test.use({
displayName: "Jeff",
user2: async ({ homeserver }, use) => {
const credentials = await homeserver.registerUser("user1234", "p4s5W0rD", "Timmy");
user2: async ({ homeserver }, use, testInfo) => {
const credentials = await homeserver.registerUser(`user2_${testInfo.testId}`, "p4s5W0rD", "Timmy");
await use(credentials);
},
});
Expand Down
6 changes: 3 additions & 3 deletions playwright/e2e/share-dialog/share-dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test.describe("Share dialog", () => {

const dialog = page.getByRole("dialog", { name: "Share room" });
await expect(dialog.getByText(`https://matrix.to/#/${room.roomId}`)).toBeVisible();
expect(dialog).toMatchScreenshot("share-dialog-room.png", {
await expect(dialog).toMatchScreenshot("share-dialog-room.png", {
// QRCode and url changes at every run
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
});
Expand All @@ -40,7 +40,7 @@ test.describe("Share dialog", () => {

const dialog = page.getByRole("dialog", { name: "Share User" });
await expect(dialog.getByText(`https://matrix.to/#/${user.userId}`)).toBeVisible();
expect(dialog).toMatchScreenshot("share-dialog-user.png", {
await expect(dialog).toMatchScreenshot("share-dialog-user.png", {
// QRCode changes at every run
mask: [page.locator(".mx_QRCode")],
});
Expand All @@ -57,7 +57,7 @@ test.describe("Share dialog", () => {

const dialog = page.getByRole("dialog", { name: "Share Room Message" });
await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked();
expect(dialog).toMatchScreenshot("share-dialog-event.png", {
await expect(dialog).toMatchScreenshot("share-dialog-event.png", {

Check failure on line 60 in playwright/e2e/share-dialog/share-dialog.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 4/6

[Chrome] › share-dialog/share-dialog.spec.ts:49:9 › Share dialog › should share an event @screenshot

1) [Chrome] › share-dialog/share-dialog.spec.ts:49:9 › Share dialog › should share an event @screenshot Error: expect(locator).toHaveScreenshot(expected) 265 pixels (ratio 0.01 of all image pixels) are different. Expected: /home/runner/work/element-web/element-web/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-event-linux.png Received: /home/runner/work/element-web/element-web/playwright/test-results/share-dialog-share-dialog-Share-dialog-should-share-an-event-Chrome/share-dialog-event-actual.png Diff: /home/runner/work/element-web/element-web/playwright/test-results/share-dialog-share-dialog-Share-dialog-should-share-an-event-Chrome/share-dialog-event-diff.png Call log: - expect.toHaveScreenshot(share-dialog-event.png) with timeout 5000ms - verifying given screenshot expectation - waiting for getByRole('dialog', { name: 'Share Room Message' }) - locator resolved to <div role="dialog" class="mx_ShareDialog" data-focus-lock-disabled="false" aria-describedby="mx_Dialog_content" aria-labelledby="mx_BaseDialog_title">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - 265 pixels (ratio 0.01 of all image pixels) are different. - waiting 100ms before taking screenshot - waiting for getByRole('dialog', { name: 'Share Room Message' }) - locator resolved to <div role="dialog" class="mx_ShareDialog" data-focus-lock-disabled="false" aria-describedby="mx_Dialog_content" aria-labelledby="mx_BaseDialog_title">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - captured a stable screenshot - 265 pixels (ratio 0.01 of all image pixels) are different. 58 | const dialog = page.getByRole("dialog", { name: "Share Room Message" }); 59 | await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked(); > 60 | await expect(dialog).toMatchScreenshot("share-dialog-event.png", { | ^ 61 | // QRCode and url changes at every run 62 | mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")], 63 | }); at /home/runner/work/element-web/element-web/playwright/e2e/share-dialog/share-dialog.spec.ts:60:30

Check failure on line 60 in playwright/e2e/share-dialog/share-dialog.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 4/6

[Chrome] › share-dialog/share-dialog.spec.ts:49:9 › Share dialog › should share an event @screenshot

1) [Chrome] › share-dialog/share-dialog.spec.ts:49:9 › Share dialog › should share an event @screenshot Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveScreenshot(expected) 265 pixels (ratio 0.01 of all image pixels) are different. Expected: /home/runner/work/element-web/element-web/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-event-linux.png Received: /home/runner/work/element-web/element-web/playwright/test-results/share-dialog-share-dialog-Share-dialog-should-share-an-event-Chrome-retry1/share-dialog-event-actual.png Diff: /home/runner/work/element-web/element-web/playwright/test-results/share-dialog-share-dialog-Share-dialog-should-share-an-event-Chrome-retry1/share-dialog-event-diff.png Call log: - expect.toHaveScreenshot(share-dialog-event.png) with timeout 5000ms - verifying given screenshot expectation - waiting for getByRole('dialog', { name: 'Share Room Message' }) - locator resolved to <div role="dialog" class="mx_ShareDialog" data-focus-lock-disabled="false" aria-describedby="mx_Dialog_content" aria-labelledby="mx_BaseDialog_title">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - 265 pixels (ratio 0.01 of all image pixels) are different. - waiting 100ms before taking screenshot - waiting for getByRole('dialog', { name: 'Share Room Message' }) - locator resolved to <div role="dialog" class="mx_ShareDialog" data-focus-lock-disabled="false" aria-describedby="mx_Dialog_content" aria-labelledby="mx_BaseDialog_title">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - captured a stable screenshot - 265 pixels (ratio 0.01 of all image pixels) are different. 58 | const dialog = page.getByRole("dialog", { name: "Share Room Message" }); 59 | await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked(); > 60 | await expect(dialog).toMatchScreenshot("share-dialog-event.png", { | ^ 61 | // QRCode and url changes at every run 62 | mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")], 63 | }); at /home/runner/work/element-web/element-web/playwright/e2e/share-dialog/share-dialog.spec.ts:60:30

Check failure on line 60 in playwright/e2e/share-dialog/share-dialog.spec.ts

View workflow job for this annotation

GitHub Actions / Run Tests [Chrome] 4/6

[Chrome] › share-dialog/share-dialog.spec.ts:49:9 › Share dialog › should share an event @screenshot

1) [Chrome] › share-dialog/share-dialog.spec.ts:49:9 › Share dialog › should share an event @screenshot Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toHaveScreenshot(expected) 265 pixels (ratio 0.01 of all image pixels) are different. Expected: /home/runner/work/element-web/element-web/playwright/snapshots/share-dialog/share-dialog.spec.ts/share-dialog-event-linux.png Received: /home/runner/work/element-web/element-web/playwright/test-results/share-dialog-share-dialog-Share-dialog-should-share-an-event-Chrome-retry2/share-dialog-event-actual.png Diff: /home/runner/work/element-web/element-web/playwright/test-results/share-dialog-share-dialog-Share-dialog-should-share-an-event-Chrome-retry2/share-dialog-event-diff.png Call log: - expect.toHaveScreenshot(share-dialog-event.png) with timeout 5000ms - verifying given screenshot expectation - waiting for getByRole('dialog', { name: 'Share Room Message' }) - locator resolved to <div role="dialog" class="mx_ShareDialog" data-focus-lock-disabled="false" aria-describedby="mx_Dialog_content" aria-labelledby="mx_BaseDialog_title">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - 265 pixels (ratio 0.01 of all image pixels) are different. - waiting 100ms before taking screenshot - waiting for getByRole('dialog', { name: 'Share Room Message' }) - locator resolved to <div role="dialog" class="mx_ShareDialog" data-focus-lock-disabled="false" aria-describedby="mx_Dialog_content" aria-labelledby="mx_BaseDialog_title">…</div> - taking element screenshot - disabled all CSS animations - waiting for fonts to load... - fonts loaded - attempting scroll into view action - waiting for element to be stable - captured a stable screenshot - 265 pixels (ratio 0.01 of all image pixels) are different. 58 | const dialog = page.getByRole("dialog", { name: "Share Room Message" }); 59 | await expect(dialog.getByRole("checkbox", { name: "Link to selected message" })).toBeChecked(); > 60 | await expect(dialog).toMatchScreenshot("share-dialog-event.png", { | ^ 61 | // QRCode and url changes at every run 62 | mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")], 63 | }); at /home/runner/work/element-web/element-web/playwright/e2e/share-dialog/share-dialog.spec.ts:60:30
// QRCode and url changes at every run
mask: [page.locator(".mx_QRCode"), page.locator(".mx_ShareDialog_top > span")],
});
Expand Down
1 change: 0 additions & 1 deletion playwright/e2e/sliding-sync/sliding-sync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ test.describe("Sliding Sync", () => {
await page.getByRole("menuitemradio", { name: "A-Z" }).dispatchEvent("click");
await expect(page.locator(".mx_StyledRadioButton_checked").getByText("A-Z")).toBeVisible();

await page.pause();
await checkOrder(["Apple", "Orange", "Pineapple", "Test Room"], page);
});

Expand Down

0 comments on commit ad0b86c

Please sign in to comment.