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

test: improve stability of Playwright tests #7139

Draft
wants to merge 40 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1918557
test(date): remove Playwright event tests that have been covered by Jest
Parsium Dec 12, 2024
8c49e74
test(date): ensure playwright tests adequately wait for element appea…
Parsium Dec 12, 2024
e44937d
test(filterable-select): improve robustness of known flaky tests
Parsium Dec 13, 2024
7614860
test(filterable-select): remove tests asserting wrapper component beh…
Parsium Dec 16, 2024
3e1aaf3
test(filterable-select): remove Playwright event tests that have been…
Parsium Dec 17, 2024
4ace3e2
test(simple-select): improve robustness of known flaky tests
Parsium Dec 17, 2024
b0ff630
test(dialog-full-screen): improve robustness of known flaky test
Parsium Dec 17, 2024
54ce225
test(simple-color-picker): remove Playwright event tests that have be…
Parsium Dec 17, 2024
116a35c
test(simple-select): remove Playwright event tests that have been cov…
Parsium Dec 17, 2024
7fe21cc
test(multi-select): remove tests asserting wrapper component behaviour
Parsium Dec 16, 2024
98cd5d0
test(simple-select): remove tests asserting wrapper component behaviour
Parsium Dec 16, 2024
1773e3a
test(toast): reduce runtime of timeout prop test
Parsium Dec 19, 2024
10b736c
test(alert): improve robustness of known flaky tests
Parsium Jan 2, 2025
9b2c3c8
test(alert): move event callback tests from Playwright to Jest to mit…
Parsium Jan 2, 2025
13623af
test(confirm): improve robustness of known flaky test
Parsium Jan 2, 2025
d022ffc
test(confirm): move event callback tests from Playwright to Jest to m…
Parsium Jan 2, 2025
f976b17
test(split-button): simplify playwright tests to reduce duplicated co…
Parsium Jan 2, 2025
a20b561
test(split-button): move keyboard tests from Playwright to Jest to mi…
Parsium Jan 2, 2025
60dae16
test(split-button): improve robustness of known flaky test
Parsium Jan 2, 2025
49d4184
test(accordion): remove Playwright event tests that have been covered…
Parsium Jan 2, 2025
ba5cf64
test(breadcrumbs): remove Playwright event tests that have been cover…
Parsium Jan 2, 2025
acd979f
test(badge): move event callback test from Playwright to Jest to miti…
Parsium Jan 2, 2025
19f0c1a
test(multi-action-button): improve robustness of known flaky test
Parsium Jan 2, 2025
e277c3b
test(filterable-select): remove hardcoded timeout in playwright test
Parsium Jan 2, 2025
c3e6485
test(flat-table): remove Playwright tests that have been covered by Jest
Parsium Jan 2, 2025
7d05af7
test(flat-table): remove tests asserting non-native behaviour
Parsium Jan 2, 2025
4ddf33d
test(global-header): remove hardcoded timeout in playwright test
Parsium Jan 3, 2025
cf42a63
test(menu): refactor playwright tests to remove hardcoded timeouts
Parsium Jan 3, 2025
5debace
test(numeral-date): remove Playwright event tests that have been cove…
Parsium Jan 3, 2025
726413c
test(multi-select): refactor playwright tests to remove hardcoded tim…
Parsium Jan 3, 2025
dd41629
test(dialog): remove hardcoded timeouts in skipped playwright tests
Parsium Jan 3, 2025
8b4d9dd
test(dialog-full-screen): remove hardcoded timeouts in skipped playwr…
Parsium Jan 3, 2025
2a108af
build: enable linting rule to flag hardcoded timeouts in Playwright t…
Parsium Jan 3, 2025
61b7225
test(multi-select): remove Playwright event tests that have been cove…
Parsium Jan 3, 2025
5b80dee
test(multi-select): improve robustness of known flaky tests
Parsium Jan 3, 2025
7b5852a
test(text-editor): improve robustness of known flaky tests
Parsium Jan 3, 2025
79c6433
test(advanced-color-picker): remove Playwright event tests that have …
Parsium Jan 3, 2025
bf39e2e
build(playwright): amend parallelisation setup for ci
Parsium Jan 6, 2025
6c105cb
test(popover-container): streamline tests to enhance performance
Parsium Jan 6, 2025
e0696ce
test(popover-container): remove Playwright tests that have been cover…
Parsium Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
"playwright/no-commented-out-tests": "error",
"playwright/no-focused-test": "error",
"playwright/no-skipped-test": "warn",
"playwright/no-wait-for-timeout": "error",
"no-return-await": "warn",
"no-return-assign": "warn",
"no-await-in-loop": "warn",
Expand All @@ -225,7 +226,8 @@
"settings": {
"playwright": {
"messages": {
"noSkippedTest": "Test skipped. If unresolved, raise a JIRA ticket or GitHub issue, then reference it in a code comment above."
"noSkippedTest": "Test skipped. If unresolved, raise a JIRA ticket or GitHub issue, then reference it in a code comment above.",
"noWaitForTimeout": "Hardcoded timeouts make tests fragile and inefficient. Use Playwright’s built-in waits (e.g. `locator.waitFor()`) or auto-retrying assertions (e.g. `await expect(locator).toBeVisible()`) to wait for conditions like element visibility or text presence."
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ jobs:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ">=20.9.0 20"

- name: Cache central NPM modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
run: |
npm ci
Expand All @@ -42,7 +45,7 @@ jobs:
run: npm run test:ct -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

- name: Upload blob report to Artifacts
if: always()
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shardIndex }}
Expand All @@ -51,7 +54,7 @@ jobs:

merge-reports:
name: "Merge reports from all shards"
if: always()
if: ${{ !cancelled() }}
needs: [playwright-tests]
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
coverage/
package-lock.json
.eslintrc
26 changes: 11 additions & 15 deletions playwright-ct.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,27 @@ const playwrightDir = resolve(__dirname, "./playwright");
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
/** Directory with the test files. */
testDir: resolve(__dirname, "./src/components"),
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */

snapshotDir: resolve(playwrightDir, "./__snapshots__"),
/* The output directory for files created during test execution */

outputDir: resolve(playwrightDir, "./test-results"),
/* Maximum time one test can run for. */

timeout: 30 * 1000,
/* Run tests in files in parallel */

fullyParallel: true,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 8 : undefined,
// Limit the number of failures on CI to save resources

retries: 3,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do we need to have the retires when run locally?

Copy link
Contributor Author

@Parsium Parsium Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I included it to help distinguish flaky tests from failing ones locally, but it's not strictly necessary.


maxFailures: process.env.CI ? 10 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */

reporter: process.env.CI
? "blob"
: [["html", { outputFolder: resolve(playwrightDir, "./test-report") }]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */

use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "retain-on-failure",
/* Port to use for Playwright component endpoint. */

ctPort: 3100,
/* Custom config for internal bundler Playwright uses for component tests. See https://playwright.dev/docs/test-components#under-the-hood */
ctViteConfig: {
Expand All @@ -45,7 +41,7 @@ export default defineConfig({
},
},
testMatch: /.*\.pw\.tsx/,
/* Configure projects for major browsers */

projects: [
{
name: "chromium",
Expand Down
18 changes: 1 addition & 17 deletions playwright/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,10 @@ import I18nProvider from "../src/components/i18n-provider/i18n-provider.componen
import { noTheme, sageTheme } from "../src/style/themes";
import enGB from "../src/locales/en-gb";
import "../src/style/fonts.css";
import * as dateLocales from "./support/date-fns-locales";

export type HooksConfig = {
validationRedesignOptIn?: boolean;
theme?: string;
localeName?: keyof typeof dateLocales;
};

const computedLocale = (str: keyof typeof dateLocales) => {
return {
locale: () => str,
date: {
dateFnsLocale: () => dateLocales[str],
ariaLabels: {
previousMonthButton: () => "Previous month",
nextMonthButton: () => "Next month",
},
},
};
};

const mountedTheme = (theme: string) => {
Expand All @@ -42,7 +27,6 @@ const mountedTheme = (theme: string) => {
beforeMount<HooksConfig>(async ({ App, hooksConfig }) => {
const {
theme = "sage",
localeName,
validationRedesignOptIn,
} = hooksConfig || {};
return (
Expand All @@ -51,7 +35,7 @@ beforeMount<HooksConfig>(async ({ App, hooksConfig }) => {
validationRedesignOptIn={validationRedesignOptIn}
>
<GlobalStyle />
<I18nProvider locale={localeName ? computedLocale(localeName) : enGB}>
<I18nProvider locale={enGB}>
<App />
</I18nProvider>
</CarbonProvider>
Expand Down
21 changes: 0 additions & 21 deletions playwright/support/date-fns-locales/index.ts

This file was deleted.

66 changes: 0 additions & 66 deletions playwright/support/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import AxeBuilder from "@axe-core/playwright";
import { expect } from "@playwright/experimental-ct-react17";
import { label, legendSpan } from "../components/index";

const OPEN_MODAL = '[data-state="open"]';
const CLOSED_MODAL = '[data-state="closed"]';

/**
* Retrieve a computed style for an element.
* @param locator The Playwright locator to evaluate (see: https://playwright.dev/docs/locators)
Expand Down Expand Up @@ -120,58 +117,6 @@ export const checkGoldenOutline = async (
);
};

export const checkElementIsInDOM = async (page: Page, locatorStr: string) => {
expect(await page.$$(locatorStr)).toHaveLength(1);
};

export const checkElementIsNotInDOM = async (
page: Page,
locatorStr: string,
) => {
expect(await page.$$(locatorStr)).toHaveLength(0);
};

export const checkDialogIsInDOM = async (page: Page) => {
await checkElementIsInDOM(page, OPEN_MODAL);
await checkElementIsNotInDOM(page, CLOSED_MODAL);
};

export const checkDialogIsNotInDOM = async (page: Page) => {
await checkElementIsNotInDOM(page, OPEN_MODAL);
await checkElementIsInDOM(page, CLOSED_MODAL);
};

/**
* Asserts if an element has event was calledOnce
* @param callbackData an array with callback data
* @param eventName {string} event name
* @example await expectEventWasCalledOnce(messages, "onClick");
*/
export const expectEventWasCalledOnce = async (
callbackData: string[],
eventName: string,
) => {
const count = JSON.stringify(callbackData.length);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const callbackName = JSON.stringify(callbackData[0]._reactName);
expect(count).toBe("1");
expect(callbackName).toBe(`"${eventName}"`);
};

/**
* Asserts that event was NOT called
* @param callbackData an array with callback data
* @example await expectEventWasNotCalled(messages);
*/
export const expectEventWasNotCalled = async (callbackData: string[]) => {
const count = JSON.stringify(callbackData.length);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(count).toBe("0");
expect(callbackData).toEqual([]);
};

/**
* Creates a safe regExp and uses the .toHaveClass() assertion
* As there is not a "contains" assertion for the .toHaveClass() assertion
Expand All @@ -193,17 +138,6 @@ export const containsClass = async (
await expect(locatorFunc).toHaveClass(classNameRegEx);
};

/**
* Uses the .toHaveFocus() assertion with a waitFor before to
* ensure the locator is available, and enough time has passed for the focus to be set.
* @param locatorFunc the locator you'd like to use
* @example await toBeFocusedDelayed(exampleLocator(page));
*/
export const toBeFocusedDelayed = async (locatorFunc: Locator) => {
await locatorFunc.waitFor({ timeout: 10000 });
await expect(locatorFunc).toBeFocused();
};

const positions = {
first: 0,
second: 1,
Expand Down
25 changes: 0 additions & 25 deletions src/components/accordion/accordion.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
getRotationAngle,
assertCssValueIsApproximately,
getStyle,
expectEventWasCalledOnce,
checkAccessibility,
} from "../../../playwright/support/helper";
import { getDataElementByValue } from "../../../playwright/components";
Expand Down Expand Up @@ -44,7 +43,7 @@
const testData = [CHARACTERS.DIACRITICS, CHARACTERS.SPECIALCHARACTERS];

// TODO: Skipped due to flaky focus behaviour. To review in FE-6428
test.skip("should have the expected styling when focused", async ({

Check warning on line 46 in src/components/accordion/accordion.pw.tsx

View workflow job for this annotation

GitHub Actions / Lint

Test skipped. If unresolved, raise a JIRA ticket or GitHub issue, then reference it in a code comment above
mount,
page,
}) => {
Expand Down Expand Up @@ -203,30 +202,6 @@
await expect(accordionContent(page)).toBeVisible();
});

[true, false].forEach((isExpanded) => {
test(`should call onChange callback when a click event is triggered and expanded is set to ${isExpanded}`, async ({
mount,
page,
}) => {
const messages: string[] = [];

await mount(
<AccordionComponent
expanded={isExpanded}
onChange={(data) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
messages.push(data);
}}
/>,
);

await accordionTitleContainer(page).click();

await expectEventWasCalledOnce(messages, "onClick");
});
});

testData.forEach((titleValue) => {
test(`should render Accordion component with ${titleValue} as a title`, async ({
mount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,83 +301,6 @@ test.describe("should render AdvancedColorPicker component and check props", ()
});
});

test.describe("should render AdvancedColorPicker component and check events", () => {
test("should call onChange callback when a click event is triggered", async ({
mount,
page,
}) => {
let callbackCount = 0;
await mount(
<AdvancedColorPickerCustom
onChange={() => {
callbackCount += 1;
}}
/>,
);

const colorToPick = simpleColorPickerInput(page, 0);
await colorToPick.click();
expect(callbackCount).toBe(1);
});

test("should call onOpen callback when a click event is triggered", async ({
mount,
page,
}) => {
let callbackCount = 0;
await mount(
<AdvancedColorPickerCustom
onOpen={() => {
callbackCount += 1;
}}
/>,
);

await closeIconButton(page).click();
const firstCell = advancedColorPickerCell(page);
await firstCell.click();
expect(callbackCount).toBe(1);
});

test("should call onClose callback when a click event is triggered", async ({
mount,
page,
}) => {
let callbackCount = 0;
await mount(
<AdvancedColorPickerCustom
onClose={() => {
callbackCount += 1;
}}
/>,
);

const closeButton = closeIconButton(page);
await closeButton.click();
expect(callbackCount).toBe(1);
});

test("should not call onBlur callback when a blur event is triggered on another color", async ({
mount,
page,
}) => {
let callbackCount = 0;
await mount(
<AdvancedColorPickerCustom
onBlur={() => {
callbackCount += 1;
}}
/>,
);

const elementToFocus = simpleColorPickerInput(page, 7);
await elementToFocus.focus();
const elementToBlur = simpleColorPickerInput(page, 7);
await elementToBlur.blur();
expect(callbackCount).toBe(0);
});
});

test.describe("Accessibility tests for AdvancedColorPicker component", () => {
test("should pass accessibility tests for AdvancedColorPicker default", async ({
mount,
Expand Down
Loading
Loading