diff --git a/.gitignore b/.gitignore index 58ae92e78..e0752845f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ log.txt .idea/ .versions/ .vscode/ -components/ +/components/ node_modules/ tmp/ dist/ @@ -23,3 +23,4 @@ www/ /test-results/ /playwright-report/ /playwright/.cache/ +/src/**/*-snapshots diff --git a/src/components/icon/test/icon.e2e.ts b/src/components/icon/test/icon.e2e.ts new file mode 100644 index 000000000..b3001897d --- /dev/null +++ b/src/components/icon/test/icon.e2e.ts @@ -0,0 +1,10 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('icon: basic', () => { + test('should not have visual regressions', async ({ page }) => { + await page.goto(`/`); + + expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(`icon-diff.png`); + }); +}); \ No newline at end of file diff --git a/src/utils/test/playwright/index.ts b/src/utils/test/playwright/index.ts new file mode 100644 index 000000000..5e91f6ccd --- /dev/null +++ b/src/utils/test/playwright/index.ts @@ -0,0 +1,2 @@ +export * from './playwright-page'; +export * from './playwright-declarations'; \ No newline at end of file diff --git a/src/utils/test/playwright/page/utils/goto.ts b/src/utils/test/playwright/page/utils/goto.ts new file mode 100644 index 000000000..03edd9e0f --- /dev/null +++ b/src/utils/test/playwright/page/utils/goto.ts @@ -0,0 +1,18 @@ +import type { Page, TestInfo } from '@playwright/test'; + +/** + * This is an extended version of Playwright's + * page.goto method. In addition to performing + * the normal page.goto work, this code also + * automatically waits for the Stencil components + * to be hydrated before proceeding with the test. + */ +export const goto = async (page: Page, url: string, options: any, _: TestInfo, originalFn: typeof page.goto) => { + + const result = await Promise.all([ + page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 }), + originalFn(url, options), + ]); + + return result[1]; +}; \ No newline at end of file diff --git a/src/utils/test/playwright/page/utils/index.ts b/src/utils/test/playwright/page/utils/index.ts new file mode 100644 index 000000000..545dd242b --- /dev/null +++ b/src/utils/test/playwright/page/utils/index.ts @@ -0,0 +1 @@ +export * from './goto'; \ No newline at end of file diff --git a/src/utils/test/playwright/playwright-declarations.ts b/src/utils/test/playwright/playwright-declarations.ts new file mode 100644 index 000000000..dd0666c05 --- /dev/null +++ b/src/utils/test/playwright/playwright-declarations.ts @@ -0,0 +1,57 @@ +import type { Page, Response } from '@playwright/test'; + +export interface E2EPage extends Page { + /** + * Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the + * last redirect. + * + * The method will throw an error if: + * - there's an SSL error (e.g. in case of self-signed certificates). + * - target URL is invalid. + * - the `timeout` is exceeded during navigation. + * - the remote server does not respond or is unreachable. + * - the main resource failed to load. + * + * The method will not throw an error when any valid HTTP status code is returned by the remote server, including 404 "Not + * Found" and 500 "Internal Server Error". The status code for such responses can be retrieved by calling + * [response.status()](https://playwright.dev/docs/api/class-response#response-status). + * + * > NOTE: The method either throws an error or returns a main resource response. The only exceptions are navigation to + * `about:blank` or navigation to the same URL with a different hash, which would succeed and return `null`. + * > NOTE: Headless mode doesn't support navigation to a PDF document. See the + * [upstream issue](https://bugs.chromium.org/p/chromium/issues/detail?id=761295). + * + * Shortcut for main frame's [frame.goto(url[, options])](https://playwright.dev/docs/api/class-frame#frame-goto) + * @param url URL to navigate page to. The url should include scheme, e.g. `https://`. When a `baseURL` via the context options was provided and the passed URL is a path, it gets merged via the + * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. + */ + goto: ( + url: string, + options?: { + /** + * Referer header value. If provided it will take preference over the referer header value set by + * [page.setExtraHTTPHeaders(headers)](https://playwright.dev/docs/api/class-page#page-set-extra-http-headers). + */ + referer?: string; + + /** + * Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be + * changed by using the + * [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-navigation-timeout), + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout), + * [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; + + /** + * When to consider operation succeeded, defaults to `load`. Events can be either: + * - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired. + * - `'load'` - consider operation to be finished when the `load` event is fired. + * - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms. + * - `'commit'` - consider operation to be finished when network response is received and the document started loading. + */ + waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; + } + ) => Promise; +} \ No newline at end of file diff --git a/src/utils/test/playwright/playwright-page.ts b/src/utils/test/playwright/playwright-page.ts new file mode 100644 index 000000000..be4c3f1a1 --- /dev/null +++ b/src/utils/test/playwright/playwright-page.ts @@ -0,0 +1,57 @@ +import type { + PlaywrightTestArgs, + PlaywrightTestOptions, + PlaywrightWorkerArgs, + PlaywrightWorkerOptions, + TestInfo, +} from '@playwright/test'; +import { test as base } from '@playwright/test'; + +import { + goto as goToPage, +} from './page/utils'; +import type { E2EPage } from './playwright-declarations'; + +type CustomTestArgs = PlaywrightTestArgs & + PlaywrightTestOptions & + PlaywrightWorkerArgs & + PlaywrightWorkerOptions & { + page: E2EPage; + }; + +type CustomFixtures = { + page: E2EPage; +}; + +/** + * Extends the base `page` test figure within Playwright. + * @param page The page to extend. + * @param testInfo The test info. + * @returns The modified playwright page with extended functionality. + */ +export async function extendPageFixture(page: E2EPage, testInfo: TestInfo) { + const originalGoto = page.goto.bind(page); + + /** + * Adds a global flag on the window that the test suite + * can use to determine when it is safe to execute tests + * on hydrated Stencil components. + */ + await page.addInitScript(` + (function() { + window.addEventListener('appload', () => { + window.testAppLoaded = true; + }); + })();`); + // Overridden Playwright methods + page.goto = (url: string, options) => goToPage(page, url, options, testInfo, originalGoto); + + return page; +} + +export const test = base.extend({ + page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { + page = await extendPageFixture(page, testInfo); + await use(page); + }, +}); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8ad868f35..1e6fad4e5 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "baseUrl": ".", "strict": true, "allowSyntheticDefaultImports": true, "allowUnreachableCode": false, @@ -15,7 +16,10 @@ "noUnusedLocals": true, "noUnusedParameters": true, "jsx": "react", - "jsxFactory": "h" + "jsxFactory": "h", + "paths": { + "@utils/*": ["src/utils/*"] + } }, "include": [ "src"