From 63cc132daa298efadb6d0ecdb580db8cf6bfaa16 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 22 Mar 2024 04:59:21 -0700 Subject: [PATCH 01/29] feat(firefox-beta): roll to r1445 (#30028) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index beaf0e4901..1ffa23ed87 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -27,7 +27,7 @@ }, { "name": "firefox-beta", - "revision": "1444", + "revision": "1445", "installByDefault": false, "browserVersion": "124.0b3" }, From b834c0b9ed631a86068fe0265b205e0aac76376a Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 22 Mar 2024 05:00:15 -0700 Subject: [PATCH 02/29] feat(webkit): roll to r1991 (#30049) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 2 +- .../playwright-core/src/server/webkit/protocol.d.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 1ffa23ed87..50540f9e58 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -33,7 +33,7 @@ }, { "name": "webkit", - "revision": "1990", + "revision": "1991", "installByDefault": true, "revisionOverrides": { "mac10.14": "1446", diff --git a/packages/playwright-core/src/server/webkit/protocol.d.ts b/packages/playwright-core/src/server/webkit/protocol.d.ts index b0e7151d19..6753488bc5 100644 --- a/packages/playwright-core/src/server/webkit/protocol.d.ts +++ b/packages/playwright-core/src/server/webkit/protocol.d.ts @@ -1569,7 +1569,7 @@ export module Protocol { /** * The reason the console is being cleared. */ - export type ClearReason = "console-api"|"main-frame-navigation"; + export type ClearReason = "console-api"|"frontend"|"main-frame-navigation"; /** * Logging channel. */ @@ -1738,6 +1738,14 @@ export module Protocol { } export type clearMessagesReturnValue = { } + /** + * Control whether calling console.clear() has an effect in Web Inspector. Defaults to true. + */ + export type setConsoleClearAPIEnabledParameters = { + enable: boolean; + } + export type setConsoleClearAPIEnabledReturnValue = { + } /** * List of the different message sources that are non-default logging channels. */ @@ -9388,6 +9396,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Console.enable": Console.enableParameters; "Console.disable": Console.disableParameters; "Console.clearMessages": Console.clearMessagesParameters; + "Console.setConsoleClearAPIEnabled": Console.setConsoleClearAPIEnabledParameters; "Console.getLoggingChannels": Console.getLoggingChannelsParameters; "Console.setLoggingChannelLevel": Console.setLoggingChannelLevelParameters; "DOM.getDocument": DOM.getDocumentParameters; @@ -9698,6 +9707,7 @@ the top of the viewport and Y increases as it proceeds towards the bottom of the "Console.enable": Console.enableReturnValue; "Console.disable": Console.disableReturnValue; "Console.clearMessages": Console.clearMessagesReturnValue; + "Console.setConsoleClearAPIEnabled": Console.setConsoleClearAPIEnabledReturnValue; "Console.getLoggingChannels": Console.getLoggingChannelsReturnValue; "Console.setLoggingChannelLevel": Console.setLoggingChannelLevelReturnValue; "DOM.getDocument": DOM.getDocumentReturnValue; From 9ce83e10f4a677e1af62018d72f754d2327eff67 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 22 Mar 2024 05:00:38 -0700 Subject: [PATCH 03/29] feat(firefox): roll to r1446 (#30053) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 ++-- packages/playwright-core/browsers.json | 8 ++++---- .../src/server/deviceDescriptorsSource.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 93be49146e..0bb303f52d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright -[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-124.0.6367.8-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-123.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) +[![npm version](https://img.shields.io/npm/v/playwright.svg)](https://www.npmjs.com/package/playwright) [![Chromium version](https://img.shields.io/badge/chromium-124.0.6367.8-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-124.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.4-blue.svg?logo=safari)](https://webkit.org/) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -10,7 +10,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | :--- | :---: | :---: | :---: | | Chromium 124.0.6367.8 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Firefox 123.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 124.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details. diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 50540f9e58..1bb07c6b29 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -15,15 +15,15 @@ }, { "name": "firefox", - "revision": "1445", + "revision": "1446", "installByDefault": true, - "browserVersion": "123.0" + "browserVersion": "124.0" }, { "name": "firefox-asan", - "revision": "1445", + "revision": "1446", "installByDefault": false, - "browserVersion": "123.0" + "browserVersion": "124.0" }, { "name": "firefox-beta", diff --git a/packages/playwright-core/src/server/deviceDescriptorsSource.json b/packages/playwright-core/src/server/deviceDescriptorsSource.json index 49a3c9446e..2a807c41f5 100644 --- a/packages/playwright-core/src/server/deviceDescriptorsSource.json +++ b/packages/playwright-core/src/server/deviceDescriptorsSource.json @@ -1472,7 +1472,7 @@ "defaultBrowserType": "chromium" }, "Desktop Firefox HiDPI": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0", "screen": { "width": 1792, "height": 1120 @@ -1532,7 +1532,7 @@ "defaultBrowserType": "chromium" }, "Desktop Firefox": { - "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0", "screen": { "width": 1920, "height": 1080 From a511731f55e3cd31dc8b4937a538ac565b9a3e82 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 22 Mar 2024 10:02:00 -0700 Subject: [PATCH 04/29] fix(html): keep projects from different bots separate (#30051) --- packages/playwright/src/reporters/html.ts | 20 ++++++++++++- tests/playwright-test/reporter-blob.spec.ts | 33 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index e0262122d1..d32e581c61 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -213,6 +213,8 @@ class HtmlBuilder { private _dataZipFile: ZipFile; private _hasTraces = false; private _attachmentsBaseURL: string; + private _projectToId: Map = new Map(); + private _lastProjectId = 0; constructor(config: FullConfig, outputDir: string, attachmentsBaseURL: string) { this._config = config; @@ -353,7 +355,7 @@ class HtmlBuilder { path = path.slice(1); const [file, ...titles] = test.titlePath(); - const testIdExpression = `[project=${projectName}]${toPosixPath(file)}\x1e${titles.join('\x1e')} (repeat:${test.repeatEachIndex})`; + const testIdExpression = `[project=${this._projectId(test.parent)}]${toPosixPath(file)}\x1e${titles.join('\x1e')} (repeat:${test.repeatEachIndex})`; const testId = fileId + '-' + calculateSha1(testIdExpression).slice(0, 20); const results = test.results.map(r => this._createTestResult(test, r)); @@ -392,6 +394,16 @@ class HtmlBuilder { }; } + private _projectId(suite: Suite): number { + const project = projectSuite(suite); + let id = this._projectToId.get(project); + if (!id) { + id = ++this._lastProjectId; + this._projectToId.set(project, id); + } + return id; + } + private _serializeAttachments(attachments: JsonAttachment[]) { let lastAttachment: TestAttachment | undefined; return attachments.map(a => { @@ -629,4 +641,10 @@ function createSnippets(stepsInFile: MultiMap) { } } +function projectSuite(suite: Suite): Suite { + while (suite.parent?.parent) + suite = suite.parent; + return suite; +} + export default HtmlReporter; diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 6e18651af9..e12ec3c069 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -1236,6 +1236,39 @@ test('preserve reportName on projects', async ({ runInlineTest, mergeReports }) expect(output).toContain(`botNames: first,second`); }); +test('keep projects with same name different bot name separate', async ({ runInlineTest, mergeReports, showReport, page }) => { + const files = (reportName: string) => ({ + 'playwright.config.ts': ` + module.exports = { + reporter: [['blob', { fileName: '${reportName}.zip' }]], + projects: [ + { name: 'foo' }, + ] + }; + `, + 'a.test.js': ` + import { test, expect } from '@playwright/test'; + test('test 1', async ({}) => { expect('${reportName}').toBe('second'); }); + `, + }); + + await runInlineTest(files('first'), undefined, { PWTEST_BOT_NAME: 'first' }); + await runInlineTest(files('second'), undefined, { PWTEST_BOT_NAME: 'second', PWTEST_BLOB_DO_NOT_REMOVE: '1' }); + + const reportDir = test.info().outputPath('blob-report'); + const { exitCode } = await mergeReports(reportDir, { 'PW_TEST_HTML_REPORT_OPEN': 'never' }, { additionalArgs: ['--reporter', 'html'] }); + expect(exitCode).toBe(0); + await showReport(); + await expect(page.locator('.subnav-item:has-text("Passed") .counter')).toHaveText('1'); + await expect(page.locator('.subnav-item:has-text("Failed") .counter')).toHaveText('1'); + + await page.getByText(/test 1.*first/).getByRole('link', { name: 'test' }).click(); + await expect(page.getByText('Errors')).toBeVisible(); + await page.goBack(); + await page.getByText(/test 1.*second/).getByRole('link', { name: 'test' }).click(); + await expect(page.getByText('Errors')).not.toBeVisible(); +}); + test('no reports error', async ({ runInlineTest, mergeReports }) => { const reportDir = test.info().outputPath('blob-report'); fs.mkdirSync(reportDir, { recursive: true }); From 4a674377046820aaefa38a4b6c9f940eccd73fe7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 22 Mar 2024 19:25:13 +0100 Subject: [PATCH 05/29] devops(gha): move from always() to !cancelled() (#30060) **Investigation:** - According to [here](https://github.com/microsoft/playwright/actions/runs/8351676198), the job got cancelled, because someone force-pushed and another commit took priority. - We have `if: always()` for uploading the blobs, even if they are cancelled. We expect, that the task before (`npx playwright test`) finished writing the blob, but it didn't - cancelled in between. We still upload - a broken one. - We download the broken zip and it breaks from there on. Proposed change: Instead of uploading always, upload if it did not get cancelled. Quoting from the GitHub Actions docs about `always()`: > Warning: Avoid using always for any task that could suffer from a critical failure, for example: getting sources, otherwise the workflow may hang until it times out. If you want to run a job or step regardless of its success or failure, use the recommended alternative: if: `${{ !cancelled() }}` This is phase 1/2 where it changes our code to use this new condition. The actual roll-out happens once it works for us. Relates https://github.com/microsoft/playwright/issues/29451 --- .github/actions/download-artifact/action.yml | 2 - .../action.yml | 3 - .github/actions/upload-blob-report/action.yml | 8 +- .github/workflows/publish_release_npm.yml | 4 +- .github/workflows/tests_electron.yml | 4 +- .github/workflows/tests_primary.yml | 24 ++-- .github/workflows/tests_secondary.yml | 114 +++++++++--------- .github/workflows/tests_service.yml | 4 +- .github/workflows/tests_stress.yml | 20 +-- .github/workflows/tests_video.yml | 2 +- .github/workflows/tests_webview2.yml | 2 +- 11 files changed, 90 insertions(+), 97 deletions(-) diff --git a/.github/actions/download-artifact/action.yml b/.github/actions/download-artifact/action.yml index d279df5f8d..3ab38a4c17 100644 --- a/.github/actions/download-artifact/action.yml +++ b/.github/actions/download-artifact/action.yml @@ -4,12 +4,10 @@ inputs: namePrefix: description: 'Name prefix of the artifacts to download' required: true - type: string default: 'blob-report' path: description: 'Directory with downloaded artifacts' required: true - type: string default: '.' runs: using: "composite" diff --git a/.github/actions/download-blob-report-from-azure/action.yml b/.github/actions/download-blob-report-from-azure/action.yml index 8026de64ea..848b98cfbc 100644 --- a/.github/actions/download-blob-report-from-azure/action.yml +++ b/.github/actions/download-blob-report-from-azure/action.yml @@ -4,16 +4,13 @@ inputs: blob_prefix: description: 'Name of the Azure blob storage directory containing blob report' required: true - type: string output_dir: description: 'Output directory where downloaded blobs will be stored' required: true - type: string default: 'blob-report' connection_string: description: 'Azure connection string' required: true - type: string runs: using: "composite" steps: diff --git a/.github/actions/upload-blob-report/action.yml b/.github/actions/upload-blob-report/action.yml index 87eda3baf8..43ef983e27 100644 --- a/.github/actions/upload-blob-report/action.yml +++ b/.github/actions/upload-blob-report/action.yml @@ -4,29 +4,27 @@ inputs: report_dir: description: 'Directory containing blob report' required: true - type: string default: 'test-results/blob-report' job_name: description: 'Unique job name' required: true - type: string default: '' runs: using: "composite" steps: - name: Upload blob report to GitHub - if: always() && github.event_name == 'pull_request' + if: ${{ !cancelled() && github.event_name == 'pull_request' }} uses: actions/upload-artifact@v4 with: name: blob-report-${{ inputs.job_name }} path: ${{ inputs.report_dir }}/** retention-days: 7 - name: Write triggering pull request number in a file - if: always() && github.event_name == 'pull_request' + if: ${{ !cancelled() && github.event_name == 'pull_request' }} shell: bash run: echo '${{ github.event.number }}' > pull_request_number.txt; - name: Upload artifact with the pull request number - if: always() && github.event_name == 'pull_request' + if: ${{ !cancelled() && github.event_name == 'pull_request' }} uses: actions/upload-artifact@v4 with: name: pull-request-${{ inputs.job_name }} diff --git a/.github/workflows/publish_release_npm.yml b/.github/workflows/publish_release_npm.yml index 3b92a01b14..46b5816834 100644 --- a/.github/workflows/publish_release_npm.yml +++ b/.github/workflows/publish_release_npm.yml @@ -25,10 +25,10 @@ jobs: - run: npm run build - run: npx playwright install-deps - run: utils/publish_all_packages.sh --release-candidate - if: "github.event.release.prerelease" + if: ${{ github.event.release.prerelease }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: utils/publish_all_packages.sh --release - if: "!github.event.release.prerelease" + if: ${{ !github.event.release.prerelease }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/tests_electron.yml b/.github/workflows/tests_electron.yml index 4427cc5e9c..05f7302747 100644 --- a/.github/workflows/tests_electron.yml +++ b/.github/workflows/tests_electron.yml @@ -42,7 +42,7 @@ jobs: - run: npm run etest if: matrix.os != 'ubuntu-latest' - run: node tests/config/checkCoverage.js electron - if: always() && matrix.os == 'ubuntu-latest' + if: ${{ !cancelled() && matrix.os == 'ubuntu-latest' }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index 8c491514ae..5b4b080287 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -58,10 +58,10 @@ jobs: - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-* - run: node tests/config/checkCoverage.js ${{ matrix.browser }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -92,10 +92,10 @@ jobs: PWTEST_CHANNEL: chromium-tip-of-tree PWTEST_BOT_NAME: "${{ matrix.os }}-chromium-tip-of-tree" - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -145,10 +145,10 @@ jobs: - run: xvfb-run npm run ttest -- --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} if: matrix.os == 'ubuntu-latest' - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -172,18 +172,18 @@ jobs: env: PWTEST_BOT_NAME: "web-components-html-reporter" - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: packages/html-reporter/blob-report job_name: "web-components-html-reporter" - run: npm run test-web - if: always() + if: ${{ !cancelled() }} env: PWTEST_BOT_NAME: "web-components-web" - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: packages/web/blob-report @@ -219,7 +219,7 @@ jobs: run: npm run test -- --workers=1 working-directory: ./playwright-vscode - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: playwright-vscode/blob-report @@ -255,10 +255,10 @@ jobs: - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run itest if: matrix.os == 'ubuntu-latest' - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index 7659c338bc..0ba6b9d848 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -45,10 +45,10 @@ jobs: - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-* - run: node tests/config/checkCoverage.js ${{ matrix.browser }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -77,10 +77,10 @@ jobs: - run: npx playwright install --with-deps ${{ matrix.browser }} chromium - run: npm run test -- --project=${{ matrix.browser }}-* - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -113,10 +113,10 @@ jobs: if: matrix.browser != 'firefox' shell: bash - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -151,7 +151,7 @@ jobs: - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run itest if: matrix.os == 'ubuntu-latest' - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash headed_tests: @@ -176,14 +176,14 @@ jobs: - run: npm run build - run: npx playwright install --with-deps ${{ matrix.browser }} chromium - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test -- --project=${{ matrix.browser }}-* --headed - if: always() && startsWith(matrix.os, 'ubuntu-') + if: ${{ !cancelled() && startsWith(matrix.os, 'ubuntu-') }} - run: npm run test -- --project=${{ matrix.browser }}-* --headed - if: always() && !startsWith(matrix.os, 'ubuntu-') + if: ${{ !cancelled() && !startsWith(matrix.os, 'ubuntu-') }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -213,10 +213,10 @@ jobs: env: PWTEST_MODE: ${{ matrix.mode }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -252,10 +252,10 @@ jobs: PWTEST_TRACE: 1 PWTEST_CHANNEL: ${{ matrix.channel }} - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -280,10 +280,10 @@ jobs: env: PWTEST_CHANNEL: chrome - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -309,10 +309,10 @@ jobs: env: PWTEST_CHANNEL: chrome - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -337,10 +337,10 @@ jobs: env: PWTEST_CHANNEL: chrome - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -371,10 +371,10 @@ jobs: - run: npm run ctest if: matrix.os != 'ubuntu-20.04' - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -405,10 +405,10 @@ jobs: - run: npm run ctest -- --headed if: matrix.os != 'ubuntu-latest' - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -433,10 +433,10 @@ jobs: env: PWTEST_CHANNEL: firefox-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -462,10 +462,10 @@ jobs: env: PWTEST_CHANNEL: firefox-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -490,10 +490,10 @@ jobs: env: PWTEST_CHANNEL: firefox-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -518,10 +518,10 @@ jobs: env: PWTEST_CHANNEL: msedge - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -547,10 +547,10 @@ jobs: env: PWTEST_CHANNEL: msedge - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -575,10 +575,10 @@ jobs: env: PWTEST_CHANNEL: msedge - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -603,10 +603,10 @@ jobs: env: PWTEST_CHANNEL: msedge-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -632,10 +632,10 @@ jobs: env: PWTEST_CHANNEL: msedge-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -660,10 +660,10 @@ jobs: env: PWTEST_CHANNEL: msedge-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -688,10 +688,10 @@ jobs: env: PWTEST_CHANNEL: msedge-dev - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -717,10 +717,10 @@ jobs: env: PWTEST_CHANNEL: msedge-dev - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -745,10 +745,10 @@ jobs: env: PWTEST_CHANNEL: msedge-dev - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -773,10 +773,10 @@ jobs: env: PWTEST_CHANNEL: chrome-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -802,10 +802,10 @@ jobs: env: PWTEST_CHANNEL: chrome-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -830,10 +830,10 @@ jobs: env: PWTEST_CHANNEL: chrome-beta - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report @@ -873,10 +873,10 @@ jobs: PLAYWRIGHT_CHROMIUM_USE_HEADLESS_NEW: 1 - run: node tests/config/checkCoverage.js chromium - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash - name: Upload blob report - if: always() + if: ${{ !cancelled() }} uses: ./.github/actions/upload-blob-report with: report_dir: blob-report diff --git a/.github/workflows/tests_service.yml b/.github/workflows/tests_service.yml index b8c192f988..2739680712 100644 --- a/.github/workflows/tests_service.yml +++ b/.github/workflows/tests_service.yml @@ -33,7 +33,7 @@ jobs: PLAYWRIGHT_SERVICE_OS: ${{ matrix.service-os }} PLAYWRIGHT_SERVICE_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }} - name: Upload blob report to GitHub - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v3 with: name: all-blob-reports @@ -43,7 +43,7 @@ jobs: merge_reports: name: "Merge reports" needs: [test] - if: always() + if: ${{ !cancelled() }} runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/tests_stress.yml b/.github/workflows/tests_stress.yml index 7c2ce79088..d0409cd585 100644 --- a/.github/workflows/tests_stress.yml +++ b/.github/workflows/tests_stress.yml @@ -37,22 +37,22 @@ jobs: - run: npx playwright install firefox-asan if: matrix.os != 'windows-latest' - run: npm run stest contexts -- --project=chromium - if: always() + if: ${{ !cancelled() }} - run: npm run stest browsers -- --project=chromium - if: always() + if: ${{ !cancelled() }} - run: npm run stest frames -- --project=chromium - if: always() + if: ${{ !cancelled() }} - run: npm run stest contexts -- --project=webkit - if: always() + if: ${{ !cancelled() }} - run: npm run stest browsers -- --project=webkit - if: always() + if: ${{ !cancelled() }} - run: npm run stest frames -- --project=webkit - if: always() + if: ${{ !cancelled() }} - run: npm run stest contexts -- --project=firefox - if: always() + if: ${{ !cancelled() }} - run: npm run stest browsers -- --project=firefox - if: always() + if: ${{ !cancelled() }} - run: npm run stest frames -- --project=firefox - if: always() + if: ${{ !cancelled() }} - run: npm run stest heap -- --project=chromium - if: always() + if: ${{ !cancelled() }} diff --git a/.github/workflows/tests_video.yml b/.github/workflows/tests_video.yml index 3acda7cec7..b51c0e4e76 100644 --- a/.github/workflows/tests_video.yml +++ b/.github/workflows/tests_video.yml @@ -36,5 +36,5 @@ jobs: env: PWTEST_VIDEO: 1 - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash diff --git a/.github/workflows/tests_webview2.yml b/.github/workflows/tests_webview2.yml index 26c97f6062..cf9a2ef0e9 100644 --- a/.github/workflows/tests_webview2.yml +++ b/.github/workflows/tests_webview2.yml @@ -46,5 +46,5 @@ jobs: Start-Process -FilePath setup.exe -Verb RunAs -Wait - run: npm run webview2test - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json - if: always() + if: ${{ !cancelled() }} shell: bash From 14cd7953f3643629ac2719e343ddc57c0dfaaba8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 22 Mar 2024 21:22:19 +0100 Subject: [PATCH 06/29] devops(merge): validate zip before uploading (#30061) --- .github/actions/upload-blob-report/action.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/actions/upload-blob-report/action.yml b/.github/actions/upload-blob-report/action.yml index 43ef983e27..72a084142a 100644 --- a/.github/actions/upload-blob-report/action.yml +++ b/.github/actions/upload-blob-report/action.yml @@ -12,6 +12,9 @@ inputs: runs: using: "composite" steps: + - name: Integrity check + shell: bash + run: find "${{ inputs.report_dir }}" -name "*.zip" -exec unzip -t {} \; - name: Upload blob report to GitHub if: ${{ !cancelled() && github.event_name == 'pull_request' }} uses: actions/upload-artifact@v4 From c1400a3db5a3dd34962ebfcd51f01c19b99acfc5 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 22 Mar 2024 13:26:22 -0700 Subject: [PATCH 07/29] Revert "feat(codegen): add button to generate toHaveScreenshot statement (#29996)" (#30064) This reverts commit 1bb463163b4efef507a4b8d8feeb48e49e4a756e. Decided to wait for a better VRT story. References #29250. --- .../src/server/injected/highlight.css | 6 ---- .../src/server/injected/recorder/recorder.ts | 25 ++-------------- .../src/server/recorder/csharp.ts | 2 -- .../src/server/recorder/java.ts | 2 -- .../src/server/recorder/javascript.ts | 2 -- .../src/server/recorder/python.ts | 30 +++++++++---------- .../src/server/recorder/recorderActions.ts | 7 +---- tests/library/inspector/cli-codegen-3.spec.ts | 14 --------- 8 files changed, 17 insertions(+), 71 deletions(-) diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 4efa63de0a..9ee0370720 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -231,12 +231,6 @@ x-pw-tool-item.value > x-div { mask-image: url("data:image/svg+xml;utf8,"); } -x-pw-tool-item.screenshot > x-div { - /* codicon: device-camera */ - -webkit-mask-image: url("data:image/svg+xml;utf8,"); - mask-image: url("data:image/svg+xml;utf8,"); -} - x-pw-tool-item.accept > x-div { -webkit-mask-image: url("data:image/svg+xml;utf8,"); mask-image: url("data:image/svg+xml;utf8,"); diff --git a/packages/playwright-core/src/server/injected/recorder/recorder.ts b/packages/playwright-core/src/server/injected/recorder/recorder.ts index e85f91c44c..2074ffa67f 100644 --- a/packages/playwright-core/src/server/injected/recorder/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder/recorder.ts @@ -761,7 +761,6 @@ class Overlay { private _assertVisibilityToggle: HTMLElement; private _assertTextToggle: HTMLElement; private _assertValuesToggle: HTMLElement; - private _assertScreenshotButton: HTMLElement; private _offsetX = 0; private _dragState: { offsetX: number, dragStart: { x: number, y: number } } | undefined; private _measure: { width: number, height: number } = { width: 0, height: 0 }; @@ -808,12 +807,6 @@ class Overlay { this._assertValuesToggle.appendChild(this._recorder.injectedScript.document.createElement('x-div')); toolsListElement.appendChild(this._assertValuesToggle); - this._assertScreenshotButton = this._recorder.injectedScript.document.createElement('x-pw-tool-item'); - this._assertScreenshotButton.title = 'Assert screenshot'; - this._assertScreenshotButton.classList.add('screenshot'); - this._assertScreenshotButton.appendChild(this._recorder.injectedScript.document.createElement('x-div')); - toolsListElement.appendChild(this._assertScreenshotButton); - this._updateVisualPosition(); this._refreshListeners(); } @@ -852,15 +845,6 @@ class Overlay { if (!this._assertValuesToggle.classList.contains('disabled')) this._recorder.delegate.setMode?.(this._recorder.state.mode === 'assertingValue' ? 'recording' : 'assertingValue'); }), - addEventListener(this._assertScreenshotButton, 'click', () => { - if (!this._assertScreenshotButton.classList.contains('disabled')) { - this._recorder.delegate.recordAction?.({ - name: 'assertScreenshot', - signals: [], - }); - this.flashToolSucceeded('assertScreenshot'); - } - }), ]; } @@ -883,7 +867,6 @@ class Overlay { this._assertTextToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); this._assertValuesToggle.classList.toggle('active', state.mode === 'assertingValue'); this._assertValuesToggle.classList.toggle('disabled', state.mode === 'none' || state.mode === 'standby' || state.mode === 'inspecting'); - this._assertScreenshotButton.classList.toggle('disabled', state.mode !== 'recording'); if (this._offsetX !== state.overlay.offsetX) { this._offsetX = state.overlay.offsetX; this._updateVisualPosition(); @@ -894,12 +877,8 @@ class Overlay { this._showOverlay(); } - flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue' | 'assertScreenshot') { - const element = { - 'assertingVisibility': this._assertVisibilityToggle, - 'assertingValue': this._assertValuesToggle, - 'assertScreenshot': this._assertScreenshotButton, - }[tool]; + flashToolSucceeded(tool: 'assertingVisibility' | 'assertingValue') { + const element = tool === 'assertingVisibility' ? this._assertVisibilityToggle : this._assertValuesToggle; element.classList.add('succeeded'); setTimeout(() => element.classList.remove('succeeded'), 2000); } diff --git a/packages/playwright-core/src/server/recorder/csharp.ts b/packages/playwright-core/src/server/recorder/csharp.ts index 2b42ae4e2c..46fadc244a 100644 --- a/packages/playwright-core/src/server/recorder/csharp.ts +++ b/packages/playwright-core/src/server/recorder/csharp.ts @@ -164,8 +164,6 @@ export class CSharpLanguageGenerator implements LanguageGenerator { const assertion = action.value ? `ToHaveValueAsync(${quote(action.value)})` : `ToBeEmptyAsync()`; return `await Expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } - case 'assertScreenshot': - return `// AssertScreenshot(await ${subject}.ScreenshotAsync());`; } } diff --git a/packages/playwright-core/src/server/recorder/java.ts b/packages/playwright-core/src/server/recorder/java.ts index 384584d272..d4ebfdeea4 100644 --- a/packages/playwright-core/src/server/recorder/java.ts +++ b/packages/playwright-core/src/server/recorder/java.ts @@ -152,8 +152,6 @@ export class JavaLanguageGenerator implements LanguageGenerator { const assertion = action.value ? `hasValue(${quote(action.value)})` : `isEmpty()`; return `assertThat(${subject}.${this._asLocator(action.selector, inFrameLocator)}).${assertion};`; } - case 'assertScreenshot': - return `// assertScreenshot(${subject}.screenshot());`; } } diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/recorder/javascript.ts index e30c6c6e59..548e0f6071 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/recorder/javascript.ts @@ -135,8 +135,6 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { const assertion = action.value ? `toHaveValue(${quote(action.value)})` : `toBeEmpty()`; return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } - case 'assertScreenshot': - return `${this._isTest ? '' : '// '}await expect(${subject}).toHaveScreenshot();`; } } diff --git a/packages/playwright-core/src/server/recorder/python.ts b/packages/playwright-core/src/server/recorder/python.ts index d9ddc0fd9c..3be02684bd 100644 --- a/packages/playwright-core/src/server/recorder/python.ts +++ b/packages/playwright-core/src/server/recorder/python.ts @@ -73,7 +73,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { if (signals.dialog) formatter.add(` ${pageAlias}.once("dialog", lambda dialog: dialog.dismiss())`); - let code = this._generateActionCall(subject, action); + let code = `${this._awaitPrefix}${this._generateActionCall(subject, action)}`; if (signals.popup) { code = `${this._asyncPrefix}with ${pageAlias}.expect_popup() as ${signals.popup.popupAlias}_info { @@ -99,7 +99,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { case 'openPage': throw Error('Not reached'); case 'closePage': - return `${this._awaitPrefix}${subject}.close()`; + return `${subject}.close()`; case 'click': { let method = 'click'; if (action.clickCount === 2) @@ -115,37 +115,35 @@ export class PythonLanguageGenerator implements LanguageGenerator { if (action.position) options.position = action.position; const optionsString = formatOptions(options, false); - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`; + return `${subject}.${this._asLocator(action.selector)}.${method}(${optionsString})`; } case 'check': - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.check()`; + return `${subject}.${this._asLocator(action.selector)}.check()`; case 'uncheck': - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.uncheck()`; + return `${subject}.${this._asLocator(action.selector)}.uncheck()`; case 'fill': - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.fill(${quote(action.text)})`; + return `${subject}.${this._asLocator(action.selector)}.fill(${quote(action.text)})`; case 'setInputFiles': - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`; + return `${subject}.${this._asLocator(action.selector)}.set_input_files(${formatValue(action.files.length === 1 ? action.files[0] : action.files)})`; case 'press': { const modifiers = toModifiers(action.modifiers); const shortcut = [...modifiers, action.key].join('+'); - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`; + return `${subject}.${this._asLocator(action.selector)}.press(${quote(shortcut)})`; } case 'navigate': - return `${this._awaitPrefix}${subject}.goto(${quote(action.url)})`; + return `${subject}.goto(${quote(action.url)})`; case 'select': - return `${this._awaitPrefix}${subject}.${this._asLocator(action.selector)}.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`; + return `${subject}.${this._asLocator(action.selector)}.select_option(${formatValue(action.options.length === 1 ? action.options[0] : action.options)})`; case 'assertText': - return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'to_contain_text' : 'to_have_text'}(${quote(action.text)})`; + return `expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? 'to_contain_text' : 'to_have_text'}(${quote(action.text)})`; case 'assertChecked': - return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).${action.checked ? 'to_be_checked()' : 'not_to_be_checked()'}`; + return `expect(${subject}.${this._asLocator(action.selector)}).${action.checked ? 'to_be_checked()' : 'not_to_be_checked()'}`; case 'assertVisible': - return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).to_be_visible()`; + return `expect(${subject}.${this._asLocator(action.selector)}).to_be_visible()`; case 'assertValue': { const assertion = action.value ? `to_have_value(${quote(action.value)})` : `to_be_empty()`; - return `${this._awaitPrefix}expect(${subject}.${this._asLocator(action.selector)}).${assertion}`; + return `expect(${subject}.${this._asLocator(action.selector)}).${assertion};`; } - case 'assertScreenshot': - return `# assert_screenshot(${this._awaitPrefix}${subject}.screenshot())`; } } diff --git a/packages/playwright-core/src/server/recorder/recorderActions.ts b/packages/playwright-core/src/server/recorder/recorderActions.ts index ff8c168dbc..3c9720cbc4 100644 --- a/packages/playwright-core/src/server/recorder/recorderActions.ts +++ b/packages/playwright-core/src/server/recorder/recorderActions.ts @@ -30,7 +30,6 @@ export type ActionName = 'assertText' | 'assertValue' | 'assertChecked' | - 'assertScreenshot' | 'assertVisible'; export type ActionBase = { @@ -120,11 +119,7 @@ export type AssertVisibleAction = ActionBase & { selector: string, }; -export type AssertScreenshotAction = ActionBase & { - name: 'assertScreenshot', -}; - -export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction | AssertScreenshotAction; +export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction | AssertVisibleAction; export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction | AssertVisibleAction; // Signals. diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts index 4afa8dd51d..96a6295f13 100644 --- a/tests/library/inspector/cli-codegen-3.spec.ts +++ b/tests/library/inspector/cli-codegen-3.spec.ts @@ -648,18 +648,4 @@ await page.GetByLabel("Coun\\"try").ClickAsync();`); expect.soft(sources1.get('Java')!.text).toContain(`assertThat(page.getByRole(AriaRole.TEXTBOX)).isVisible()`); expect.soft(sources1.get('C#')!.text).toContain(`await Expect(page.GetByRole(AriaRole.Textbox)).ToBeVisibleAsync()`); }); - - test('should assert screenshot', async ({ openRecorder }) => { - const recorder = await openRecorder(); - await recorder.setContentAndWait(`
Hello, world
`); - const [sources] = await Promise.all([ - recorder.waitForOutput('JavaScript', 'toHaveScreenshot'), - recorder.page.click('x-pw-tool-item.screenshot'), - ]); - expect.soft(sources.get('JavaScript')!.text).toContain(`await expect(page).toHaveScreenshot()`); - expect.soft(sources.get('Python')!.text).toContain(`# assert_screenshot(page.screenshot())`); - expect.soft(sources.get('Python Async')!.text).toContain(`# assert_screenshot(await page.screenshot())`); - expect.soft(sources.get('Java')!.text).toContain(`// assertScreenshot(page.screenshot());`); - expect.soft(sources.get('C#')!.text).toContain(`// AssertScreenshot(await page.ScreenshotAsync())`); - }); }); From 2443d6560f8390681524662a18caaa7218dbd176 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:27:33 -0700 Subject: [PATCH 08/29] feat(firefox-beta): roll to r1446 (#30062) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/playwright-core/browsers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 1bb07c6b29..900a79f047 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -27,9 +27,9 @@ }, { "name": "firefox-beta", - "revision": "1445", + "revision": "1446", "installByDefault": false, - "browserVersion": "124.0b3" + "browserVersion": "125.0b3" }, { "name": "webkit", From c8e8d8f8bb3a00bd9fa727e10cbab72fc41d5617 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 22 Mar 2024 13:32:28 -0700 Subject: [PATCH 09/29] test: fulfilling with gzip should allow to read the body (#30065) Fixes #29261. --- tests/page/page-request-fulfill.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/page/page-request-fulfill.spec.ts b/tests/page/page-request-fulfill.spec.ts index c76fbd2ab7..9a568e8369 100644 --- a/tests/page/page-request-fulfill.spec.ts +++ b/tests/page/page-request-fulfill.spec.ts @@ -438,3 +438,19 @@ it('should fulfill json', async ({ page, server }) => { expect(response.headers()['content-type']).toBe('application/json'); expect(body).toBe(JSON.stringify({ bar: 'baz' })); }); + +it('should fulfill with gzip and readback', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29261' }, +}, async ({ page, server }) => { + server.enableGzip('/one-style.html'); + await page.route('**/one-style.html', async route => { + const response = await route.fetch(); + expect(response.headers()['content-encoding']).toBe('gzip'); + await route.fulfill({ response }); + }); + + const response = await page.goto(server.PREFIX + '/one-style.html'); + await expect(page.locator('div')).toHaveText('hello, world!'); + await expect(page.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)'); + expect(await response.text()).toContain(`
hello, world!
`); +}); From ee9432b9daba33afd226483ea31ff0115552a29b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 22 Mar 2024 13:49:28 -0700 Subject: [PATCH 10/29] chore: send test params over the wire in ui mode (#30046) --- .../src/server/trace/viewer/traceViewer.ts | 41 +++- .../src/isomorphic/testServerConnection.ts | 49 +++-- .../src/isomorphic/testServerInterface.ts | 30 +-- packages/playwright/src/program.ts | 35 ++- packages/playwright/src/reporters/html.ts | 7 +- packages/playwright/src/reporters/merge.ts | 2 +- packages/playwright/src/runner/reporters.ts | 14 +- packages/playwright/src/runner/runner.ts | 39 +--- packages/playwright/src/runner/testServer.ts | 199 ++++++++++++------ packages/trace-viewer/src/ui/uiModeView.tsx | 47 +++-- 10 files changed, 289 insertions(+), 174 deletions(-) diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index aa6ef60271..f3ed7fa06c 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -36,6 +36,14 @@ export type TraceViewerServerOptions = { }; export type TraceViewerRedirectOptions = { + args?: string[]; + grep?: string; + grepInvert?: string; + project?: string[]; + workers?: number | string; + headed?: boolean; + timeout?: number; + reporter?: string[]; webApp?: string; isServer?: boolean; }; @@ -102,19 +110,36 @@ export async function startTraceViewerServer(options?: TraceViewerServerOptions) } export async function installRootRedirect(server: HttpServer, traceUrls: string[], options: TraceViewerRedirectOptions) { - const params = (traceUrls || []).map(t => `trace=${encodeURIComponent(t)}`); + const params = new URLSearchParams(); + for (const traceUrl of traceUrls) + params.append('trace', traceUrl); if (server.wsGuid()) - params.push('ws=' + server.wsGuid()); + params.append('ws', server.wsGuid()!); if (options?.isServer) - params.push('isServer'); + params.append('isServer', ''); if (isUnderTest()) - params.push('isUnderTest=true'); - const searchQuery = params.length ? '?' + params.join('&') : ''; - const urlPath = `/trace/${options.webApp || 'index.html'}${searchQuery}`; + params.append('isUnderTest', 'true'); + for (const arg of options.args || []) + params.append('arg', arg); + if (options.grep) + params.append('grep', options.grep); + if (options.grepInvert) + params.append('grepInvert', options.grepInvert); + for (const project of options.project || []) + params.append('project', project); + if (options.workers) + params.append('workers', String(options.workers)); + if (options.timeout) + params.append('timeout', String(options.timeout)); + if (options.headed) + params.append('headed', ''); + for (const reporter of options.reporter || []) + params.append('reporter', reporter); - server.routePath('/', (request, response) => { + const urlPath = `/trace/${options.webApp || 'index.html'}?${params.toString()}`; + server.routePath('/', (_, response) => { response.statusCode = 302; - response.setHeader('Location', urlPath + request.url!.substring(1)); + response.setHeader('Location', urlPath); response.end(); return true; }); diff --git a/packages/playwright/src/isomorphic/testServerConnection.ts b/packages/playwright/src/isomorphic/testServerConnection.ts index 3a07fbcb31..b33d56ebde 100644 --- a/packages/playwright/src/isomorphic/testServerConnection.ts +++ b/packages/playwright/src/isomorphic/testServerConnection.ts @@ -15,7 +15,6 @@ */ import type { TestServerInterface, TestServerInterfaceEvents } from '@testIsomorphic/testServerInterface'; -import type * as reporterTypes from 'playwright/types/testReporter'; import * as events from './events'; export class TestServerConnection implements TestServerInterface, TestServerInterfaceEvents { @@ -67,7 +66,7 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this._connectedPromise = new Promise((f, r) => { this._ws.addEventListener('open', () => { f(); - this._ws.send(JSON.stringify({ method: 'ready' })); + this._ws.send(JSON.stringify({ id: -1, method: 'ready' })); }); this._ws.addEventListener('error', r); }); @@ -77,6 +76,10 @@ export class TestServerConnection implements TestServerInterface, TestServerInte }); } + connect() { + return this._connectedPromise; + } + private async _sendMessage(method: string, params?: any): Promise { const logForTest = (globalThis as any).__logForTest; logForTest?.({ method, params }); @@ -103,81 +106,83 @@ export class TestServerConnection implements TestServerInterface, TestServerInte this._onListChangedEmitter.fire(params); else if (method === 'testFilesChanged') this._onTestFilesChangedEmitter.fire(params); + else if (method === 'loadTraceRequested') + this._onLoadTraceRequestedEmitter.fire(params); } - async ping(): Promise { + async ping(params: Parameters[0]): ReturnType { await this._sendMessage('ping'); } - async pingNoReply() { + async pingNoReply(params: Parameters[0]) { this._sendMessageNoReply('ping'); } - async watch(params: { fileNames: string[]; }): Promise { + async watch(params: Parameters[0]): ReturnType { await this._sendMessage('watch', params); } - watchNoReply(params: { fileNames: string[]; }) { + watchNoReply(params: Parameters[0]) { this._sendMessageNoReply('watch', params); } - async open(params: { location: reporterTypes.Location; }): Promise { + async open(params: Parameters[0]): ReturnType { await this._sendMessage('open', params); } - openNoReply(params: { location: reporterTypes.Location; }) { + openNoReply(params: Parameters[0]) { this._sendMessageNoReply('open', params); } - async resizeTerminal(params: { cols: number; rows: number; }): Promise { + async resizeTerminal(params: Parameters[0]): ReturnType { await this._sendMessage('resizeTerminal', params); } - resizeTerminalNoReply(params: { cols: number; rows: number; }) { + resizeTerminalNoReply(params: Parameters[0]) { this._sendMessageNoReply('resizeTerminal', params); } - async checkBrowsers(): Promise<{ hasBrowsers: boolean; }> { + async checkBrowsers(params: Parameters[0]): ReturnType { return await this._sendMessage('checkBrowsers'); } - async installBrowsers(): Promise { + async installBrowsers(params: Parameters[0]): ReturnType { await this._sendMessage('installBrowsers'); } - async runGlobalSetup(): Promise<'passed' | 'failed' | 'timedout' | 'interrupted'> { + async runGlobalSetup(params: Parameters[0]): ReturnType { return await this._sendMessage('runGlobalSetup'); } - async runGlobalTeardown(): Promise<'passed' | 'failed' | 'timedout' | 'interrupted'> { + async runGlobalTeardown(params: Parameters[0]): ReturnType { return await this._sendMessage('runGlobalTeardown'); } - async listFiles(): Promise<{ projects: { name: string; testDir: string; use: { testIdAttribute?: string | undefined; }; files: string[]; }[]; cliEntryPoint?: string | undefined; error?: reporterTypes.TestError | undefined; }> { - return await this._sendMessage('listFiles'); + async listFiles(params: Parameters[0]): ReturnType { + return await this._sendMessage('listFiles', params); } - async listTests(params: { reporter?: string | undefined; fileNames?: string[] | undefined; }): Promise<{ report: any[] }> { + async listTests(params: Parameters[0]): ReturnType { return await this._sendMessage('listTests', params); } - async runTests(params: { reporter?: string | undefined; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }): Promise<{ status: reporterTypes.FullResult['status'] }> { + async runTests(params: Parameters[0]): ReturnType { return await this._sendMessage('runTests', params); } - async findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: reporterTypes.TestError[] | undefined; }> { + async findRelatedTestFiles(params: Parameters[0]): ReturnType { return await this._sendMessage('findRelatedTestFiles', params); } - async stopTests(): Promise { + async stopTests(params: Parameters[0]): ReturnType { await this._sendMessage('stopTests'); } - stopTestsNoReply() { + stopTestsNoReply(params: Parameters[0]) { this._sendMessageNoReply('stopTests'); } - async closeGracefully(): Promise { + async closeGracefully(params: Parameters[0]): ReturnType { await this._sendMessage('closeGracefully'); } } diff --git a/packages/playwright/src/isomorphic/testServerInterface.ts b/packages/playwright/src/isomorphic/testServerInterface.ts index 77fdd6049c..d86d37d40e 100644 --- a/packages/playwright/src/isomorphic/testServerInterface.ts +++ b/packages/playwright/src/isomorphic/testServerInterface.ts @@ -18,7 +18,7 @@ import type * as reporterTypes from '../../types/testReporter'; import type { Event } from './events'; export interface TestServerInterface { - ping(): Promise; + ping(params: {}): Promise; watch(params: { fileNames: string[]; @@ -28,15 +28,17 @@ export interface TestServerInterface { resizeTerminal(params: { cols: number, rows: number }): Promise; - checkBrowsers(): Promise<{ hasBrowsers: boolean }>; + checkBrowsers(params: {}): Promise<{ hasBrowsers: boolean }>; - installBrowsers(): Promise; + installBrowsers(params: {}): Promise; - runGlobalSetup(): Promise; + runGlobalSetup(params: {}): Promise; - runGlobalTeardown(): Promise; + runGlobalTeardown(params: {}): Promise; - listFiles(): Promise<{ + listFiles(params: { + projects?: string[]; + }): Promise<{ projects: { name: string; testDir: string; @@ -51,17 +53,21 @@ export interface TestServerInterface { * Returns list of teleReporter events. */ listTests(params: { - reporter?: string; - fileNames?: string[]; + serializer?: string; + projects?: string[]; + locations?: string[]; }): Promise<{ report: any[] }>; runTests(params: { - reporter?: string; + serializer?: string; locations?: string[]; grep?: string; + grepInvert?: string; testIds?: string[]; headed?: boolean; - oneWorker?: boolean; + workers?: number | string; + timeout?: number, + reporters?: string[], trace?: 'on' | 'off'; projects?: string[]; reuseContext?: boolean; @@ -72,9 +78,9 @@ export interface TestServerInterface { files: string[]; }): Promise<{ testFiles: string[]; errors?: reporterTypes.TestError[]; }>; - stopTests(): Promise; + stopTests(params: {}): Promise; - closeGracefully(): Promise; + closeGracefully(params: {}): Promise; } export interface TestServerInterfaceEvents { diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index bb65d1982a..d16a47d4e6 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -34,6 +34,7 @@ export { program } from 'playwright-core/lib/cli/program'; import type { ReporterDescription } from '../types/test'; import { prepareErrorStack } from './reporters/base'; import { cacheDir } from './transform/compilationCache'; +import * as testServer from './runner/testServer'; function addTestCommand(program: Command) { const command = program.command('test [test-filter...]'); @@ -151,7 +152,28 @@ Examples: async function runTests(args: string[], opts: { [key: string]: any }) { await startProfiling(); - const config = await loadConfigFromFileRestartIfNeeded(opts.config, overridesFromOptions(opts), opts.deps === false); + const cliOverrides = overridesFromOptions(opts); + + if (opts.ui || opts.uiHost || opts.uiPort) { + const status = await testServer.runUIMode(opts.config, { + host: opts.uiHost, + port: opts.uiPort ? +opts.uiPort : undefined, + args, + grep: opts.grep as string | undefined, + grepInvert: opts.grepInvert as string | undefined, + project: opts.project || undefined, + headed: opts.headed, + reporter: Array.isArray(opts.reporter) ? opts.reporter : opts.reporter ? [opts.reporter] : undefined, + workers: cliOverrides.workers, + timeout: cliOverrides.timeout, + }); + await stopProfiling('runner'); + const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); + gracefullyProcessExitDoNotHang(exitCode); + return; + } + + const config = await loadConfigFromFileRestartIfNeeded(opts.config, cliOverrides, opts.deps === false); if (!config) return; @@ -164,9 +186,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) { const runner = new Runner(config); let status: FullResult['status']; - if (opts.ui || opts.uiHost || opts.uiPort) - status = await runner.runUIMode({ host: opts.uiHost, port: opts.uiPort ? +opts.uiPort : undefined }); - else if (process.env.PWTEST_WATCH) + if (process.env.PWTEST_WATCH) status = await runner.watchAllTests(); else status = await runner.runAllTests(); @@ -176,14 +196,9 @@ async function runTests(args: string[], opts: { [key: string]: any }) { } async function runTestServer(opts: { [key: string]: any }) { - const config = await loadConfigFromFileRestartIfNeeded(opts.config, overridesFromOptions(opts), opts.deps === false); - if (!config) - return; - config.cliPassWithNoTests = true; - const runner = new Runner(config); const host = opts.host || 'localhost'; const port = opts.port ? +opts.port : 0; - const status = await runner.runTestServer({ host, port }); + const status = await testServer.runTestServer(opts.config, { host, port }); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); gracefullyProcessExitDoNotHang(exitCode); } diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index d32e581c61..8f38346c83 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -51,7 +51,8 @@ type HtmlReporterOptions = { host?: string, port?: number, attachmentsBaseURL?: string, - _mode?: string; + _mode?: 'test' | 'list'; + _isTestServer?: boolean; }; class HtmlReporter extends EmptyReporter { @@ -67,6 +68,8 @@ class HtmlReporter extends EmptyReporter { constructor(options: HtmlReporterOptions) { super(); this._options = options; + if (options._mode === 'test') + process.env.PW_HTML_REPORT = '1'; } override printsToStdio() { @@ -125,7 +128,7 @@ class HtmlReporter extends EmptyReporter { if (process.env.CI || !this._buildResult) return; const { ok, singleTestId } = this._buildResult; - const shouldOpen = this._open === 'always' || (!ok && this._open === 'on-failure'); + const shouldOpen = !this._options._isTestServer && (this._open === 'always' || (!ok && this._open === 'on-failure')); if (shouldOpen) { await showHTMLReport(this._outputFolder, this._options.host, this._options.port, singleTestId); } else if (this._options._mode === 'test') { diff --git a/packages/playwright/src/reporters/merge.ts b/packages/playwright/src/reporters/merge.ts index ef34c409cc..57398ebc4e 100644 --- a/packages/playwright/src/reporters/merge.ts +++ b/packages/playwright/src/reporters/merge.ts @@ -37,7 +37,7 @@ type ReportData = { }; export async function createMergedReport(config: FullConfigInternal, dir: string, reporterDescriptions: ReporterDescription[], rootDirOverride: string | undefined) { - const reporters = await createReporters(config, 'merge', reporterDescriptions); + const reporters = await createReporters(config, 'merge', false, reporterDescriptions); const multiplexer = new Multiplexer(reporters); const stringPool = new StringInternPool(); diff --git a/packages/playwright/src/runner/reporters.ts b/packages/playwright/src/runner/reporters.ts index 7e846c493f..3fc633f6e6 100644 --- a/packages/playwright/src/runner/reporters.ts +++ b/packages/playwright/src/runner/reporters.ts @@ -33,7 +33,7 @@ import { BlobReporter } from '../reporters/blob'; import type { ReporterDescription } from '../../types/test'; import { type ReporterV2, wrapReporterAsV2 } from '../reporters/reporterV2'; -export async function createReporters(config: FullConfigInternal, mode: 'list' | 'test' | 'ui' | 'merge', descriptions?: ReporterDescription[]): Promise { +export async function createReporters(config: FullConfigInternal, mode: 'list' | 'test' | 'merge', isTestServer: boolean, descriptions?: ReporterDescription[]): Promise { const defaultReporters: { [key in BuiltInReporter]: new(arg: any) => ReporterV2 } = { blob: BlobReporter, dot: mode === 'list' ? ListModeReporter : DotReporter, @@ -43,14 +43,14 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' | json: JSONReporter, junit: JUnitReporter, null: EmptyReporter, - html: mode === 'ui' ? LineReporter : HtmlReporter, + html: HtmlReporter, markdown: MarkdownReporter, }; const reporters: ReporterV2[] = []; descriptions ??= config.config.reporter; if (config.configCLIOverrides.additionalReporters) descriptions = [...descriptions, ...config.configCLIOverrides.additionalReporters]; - const runOptions = reporterOptions(config, mode); + const runOptions = reporterOptions(config, mode, isTestServer); for (const r of descriptions) { const [name, arg] = r; const options = { ...runOptions, ...arg }; @@ -78,17 +78,19 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' | return reporters; } -export async function createReporterForTestServer(config: FullConfigInternal, file: string, mode: 'test' | 'list', messageSink: (message: any) => void): Promise { +export async function createReporterForTestServer(config: FullConfigInternal, mode: 'list' | 'test', file: string, messageSink: (message: any) => void): Promise { const reporterConstructor = await loadReporter(config, file); - const runOptions = reporterOptions(config, mode, messageSink); + const runOptions = reporterOptions(config, mode, true, messageSink); const instance = new reporterConstructor(runOptions); return wrapReporterAsV2(instance); } -function reporterOptions(config: FullConfigInternal, mode: 'list' | 'test' | 'ui' | 'merge', send?: (message: any) => void) { +function reporterOptions(config: FullConfigInternal, mode: 'list' | 'test' | 'merge', isTestServer: boolean, send?: (message: any) => void) { return { configDir: config.configDir, _send: send, + _mode: mode, + _isTestServer: isTestServer, }; } diff --git a/packages/playwright/src/runner/runner.ts b/packages/playwright/src/runner/runner.ts index 4a2d329159..e6ecf84d00 100644 --- a/packages/playwright/src/runner/runner.ts +++ b/packages/playwright/src/runner/runner.ts @@ -16,8 +16,7 @@ */ import path from 'path'; -import type { HttpServer, ManualPromise } from 'playwright-core/lib/utils'; -import { isUnderTest, monotonicTime } from 'playwright-core/lib/utils'; +import { monotonicTime } from 'playwright-core/lib/utils'; import type { FullResult, TestError } from '../../types/testReporter'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import { collectFilesForProject, filterProjects } from './projectUtils'; @@ -26,13 +25,11 @@ import { TestRun, createTaskRunner, createTaskRunnerForList } from './tasks'; import type { FullConfigInternal } from '../common/config'; import { colors } from 'playwright-core/lib/utilsBundle'; import { runWatchModeLoop } from './watchMode'; -import { runTestServer } from './testServer'; import { InternalReporter } from '../reporters/internalReporter'; import { Multiplexer } from '../reporters/multiplexer'; import type { Suite } from '../common/test'; import { wrapReporterAsV2 } from '../reporters/reporterV2'; import { affectedTestFiles } from '../transform/compilationCache'; -import { installRootRedirect, openTraceInBrowser, openTraceViewerApp } from 'playwright-core/lib/server'; type ProjectConfigWithFiles = { name: string; @@ -59,9 +56,9 @@ export class Runner { this._config = config; } - async listTestFiles(): Promise { + async listTestFiles(projectNames?: string[]): Promise { const frameworkPackage = (this._config.config as any)['@playwright/test']?.['packageJSON']; - const projects = filterProjects(this._config.projects); + const projects = filterProjects(this._config.projects, projectNames); const report: ConfigListFilesReport = { projects: [], cliEntryPoint: frameworkPackage ? path.join(path.dirname(frameworkPackage), 'cli.js') : undefined, @@ -85,7 +82,7 @@ export class Runner { // Legacy webServer support. webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); - const reporter = new InternalReporter(new Multiplexer(await createReporters(config, listOnly ? 'list' : 'test'))); + const reporter = new InternalReporter(new Multiplexer(await createReporters(config, listOnly ? 'list' : 'test', false))); const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process', { failOnLoadErrors: true }) : createTaskRunner(config, reporter); @@ -148,34 +145,6 @@ export class Runner { return await runWatchModeLoop(config); } - async runUIMode(options: { host?: string, port?: number }): Promise { - const config = this._config; - webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); - return await runTestServer(config, options, async (server: HttpServer, cancelPromise: ManualPromise) => { - await installRootRedirect(server, [], { webApp: 'uiMode.html' }); - if (options.host !== undefined || options.port !== undefined) { - await openTraceInBrowser(server.urlPrefix()); - } else { - const page = await openTraceViewerApp(server.urlPrefix(), 'chromium', { - headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== '1', - persistentContextOptions: { - handleSIGINT: false, - }, - }); - page.on('close', () => cancelPromise.resolve()); - } - }); - } - - async runTestServer(options: { host?: string, port?: number }): Promise { - const config = this._config; - webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); - return await runTestServer(config, options, async server => { - // eslint-disable-next-line no-console - console.log('Listening on ' + server.urlPrefix().replace('http:', 'ws:') + '/' + server.wsGuid()); - }); - } - async findRelatedTestFiles(mode: 'in-process' | 'out-of-process', files: string[]): Promise { const result = await this.loadAllTests(mode); if (result.status !== 'passed' || !result.suite) diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index b915834d24..33cc6e4f9e 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -16,7 +16,7 @@ import fs from 'fs'; import path from 'path'; -import { registry, startTraceViewerServer } from 'playwright-core/lib/server'; +import { installRootRedirect, openTraceInBrowser, openTraceViewerApp, registry, startTraceViewerServer } from 'playwright-core/lib/server'; import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'playwright-core/lib/utils'; import type { Transport, HttpServer } from 'playwright-core/lib/utils'; import type * as reporterTypes from '../../types/testReporter'; @@ -34,33 +34,26 @@ import type { TestServerInterface, TestServerInterfaceEventEmitters } from '../i import { Runner } from './runner'; import { serializeError } from '../util'; import { prepareErrorStack } from '../reporters/base'; +import type { ConfigCLIOverrides } from '../common/ipc'; +import { loadConfig, resolveConfigFile, restartWithExperimentalTsEsm } from '../common/configLoader'; +import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; +import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playwright-core/lib/server/trace/viewer/traceViewer'; +import type { TestRunnerPluginRegistration } from '../plugins'; class TestServer { - private _config: FullConfigInternal; + private _configFile: string | undefined; private _dispatcher: TestServerDispatcher | undefined; private _originalStdoutWrite: NodeJS.WriteStream['write']; private _originalStderrWrite: NodeJS.WriteStream['write']; - constructor(config: FullConfigInternal) { - this._config = config; - process.env.PW_LIVE_TRACE_STACKS = '1'; - config.cliListOnly = false; - config.cliPassWithNoTests = true; - config.config.preserveOutput = 'always'; - - for (const p of config.projects) { - p.project.retries = 0; - p.project.repeatEach = 1; - } - config.configCLIOverrides.use = config.configCLIOverrides.use || {}; - config.configCLIOverrides.use.trace = { mode: 'on', sources: false, _live: true }; - + constructor(configFile: string | undefined) { + this._configFile = configFile; this._originalStdoutWrite = process.stdout.write; this._originalStderrWrite = process.stderr.write; } async start(options: { host?: string, port?: number }): Promise { - this._dispatcher = new TestServerDispatcher(this._config); + this._dispatcher = new TestServerDispatcher(this._configFile); return await startTraceViewerServer({ ...options, transport: this._dispatcher.transport }); } @@ -90,7 +83,7 @@ class TestServer { } class TestServerDispatcher implements TestServerInterface { - private _config: FullConfigInternal; + private _configFile: string | undefined; private _globalWatcher: Watcher; private _testWatcher: Watcher; private _testRun: { run: Promise, stop: ManualPromise } | undefined; @@ -98,9 +91,10 @@ class TestServerDispatcher implements TestServerInterface { private _queue = Promise.resolve(); private _globalCleanup: (() => Promise) | undefined; readonly _dispatchEvent: TestServerInterfaceEventEmitters['dispatchEvent']; + private _plugins: TestRunnerPluginRegistration[] | undefined; - constructor(config: FullConfigInternal) { - this._config = config; + constructor(configFile: string | undefined) { + this._configFile = configFile; this.transport = { dispatch: (method, params) => (this as any)[method](params), onclose: () => {}, @@ -114,16 +108,18 @@ class TestServerDispatcher implements TestServerInterface { this._dispatchEvent = (method, params) => this.transport.sendEvent?.(method, params); } + async ready() {} + async ping() {} - async open(params: { location: reporterTypes.Location }) { + async open(params: Parameters[0]): ReturnType { if (isUnderTest()) return; // eslint-disable-next-line no-console open('vscode://file/' + params.location.file + ':' + params.location.line).catch(e => console.error(e)); } - async resizeTerminal(params: { cols: number; rows: number; }) { + async resizeTerminal(params: Parameters[0]): ReturnType { process.stdout.columns = params.cols; process.stdout.rows = params.rows; process.stderr.columns = params.cols; @@ -141,10 +137,13 @@ class TestServerDispatcher implements TestServerInterface { async runGlobalSetup(): Promise { await this.runGlobalTeardown(); + const config = await this._loadConfig(this._configFile); + webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); + const reporter = new InternalReporter(new ListReporter()); - const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter); - reporter.onConfigure(this._config.config); - const testRun = new TestRun(this._config, reporter); + const taskRunner = createTaskRunnerForWatchSetup(config, reporter); + reporter.onConfigure(config.config); + const testRun = new TestRun(config, reporter); const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0); await reporter.onEnd({ status }); await reporter.onExit(); @@ -162,10 +161,11 @@ class TestServerDispatcher implements TestServerInterface { return result; } - async listFiles() { + async listFiles(params: Parameters[0]): ReturnType { try { - const runner = new Runner(this._config); - return runner.listTestFiles(); + const config = await this._loadConfig(this._configFile); + const runner = new Runner(config); + return runner.listTestFiles(params.projects); } catch (e) { const error: reporterTypes.TestError = serializeError(e); error.location = prepareErrorStack(e.stack).location; @@ -173,82 +173,109 @@ class TestServerDispatcher implements TestServerInterface { } } - async listTests(params: { reporter?: string; fileNames: string[]; }) { - let report: any[] = []; + async listTests(params: Parameters[0]): ReturnType { + let result: Awaited>; this._queue = this._queue.then(async () => { - report = await this._innerListTests(params); + result = await this._innerListTests(params); }).catch(printInternalError); await this._queue; - return { report }; + return result!; } - private async _innerListTests(params: { reporter?: string; fileNames?: string[]; }) { + private async _innerListTests(params: Parameters[0]): ReturnType { + const overrides: ConfigCLIOverrides = { + repeatEach: 1, + retries: 0, + }; + const config = await this._loadConfig(this._configFile, overrides); + config.cliArgs = params.locations || []; + config.cliProjectFilter = params.projects?.length ? params.projects : undefined; + config.cliListOnly = true; + + const wireReporter = await createReporterForTestServer(config, 'list', params.serializer || require.resolve('./uiModeReporter'), e => report.push(e)); const report: any[] = []; - const wireReporter = await createReporterForTestServer(this._config, params.reporter || require.resolve('./uiModeReporter'), 'list', e => report.push(e)); const reporter = new InternalReporter(wireReporter); - this._config.cliArgs = params.fileNames || []; - this._config.cliListOnly = true; - this._config.testIdMatcher = undefined; - const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process', { failOnLoadErrors: false }); - const testRun = new TestRun(this._config, reporter); - reporter.onConfigure(this._config.config); + + const taskRunner = createTaskRunnerForList(config, reporter, 'out-of-process', { failOnLoadErrors: false }); + const testRun = new TestRun(config, reporter); + reporter.onConfigure(config.config); const status = await taskRunner.run(testRun, 0); await reporter.onEnd({ status }); await reporter.onExit(); const projectDirs = new Set(); const projectOutputs = new Set(); - for (const p of this._config.projects) { + for (const p of config.projects) { projectDirs.add(p.project.testDir); projectOutputs.add(p.project.outputDir); } - const result = await resolveCtDirs(this._config); + const result = await resolveCtDirs(config); if (result) { projectDirs.add(result.templateDir); projectOutputs.add(result.outDir); } this._globalWatcher.update([...projectDirs], [...projectOutputs], false); - return report; + return { report }; } - async runTests(params: { reporter?: string; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }) { - let status: reporterTypes.FullResult['status']; + async runTests(params: Parameters[0]): ReturnType { + let result: Awaited>; this._queue = this._queue.then(async () => { - status = await this._innerRunTests(params).catch(printInternalError) || 'failed'; + result = await this._innerRunTests(params).catch(printInternalError) || { status: 'failed' }; }); await this._queue; - return { status: status! }; + return result!; } - private async _innerRunTests(params: { reporter?: string; locations?: string[] | undefined; grep?: string | undefined; testIds?: string[] | undefined; headed?: boolean | undefined; oneWorker?: boolean | undefined; trace?: 'off' | 'on' | undefined; projects?: string[] | undefined; reuseContext?: boolean | undefined; connectWsEndpoint?: string | undefined; }): Promise { + private async _innerRunTests(params: Parameters[0]): ReturnType { await this.stopTests(); - const { testIds, projects, locations, grep } = params; + const overrides: ConfigCLIOverrides = { + repeatEach: 1, + retries: 0, + preserveOutputDir: true, + timeout: params.timeout, + reporter: params.reporters ? params.reporters.map(r => [r]) : undefined, + use: { + trace: params.trace === 'on' ? { mode: 'on', sources: false, _live: true } : undefined, + headless: params.headed ? false : undefined, + _optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined, + _optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined, + }, + workers: params.workers, + }; + if (params.trace === 'on') + process.env.PW_LIVE_TRACE_STACKS = '1'; + else + process.env.PW_LIVE_TRACE_STACKS = undefined; - const testIdSet = testIds ? new Set(testIds) : null; - this._config.cliArgs = locations ? locations : []; - this._config.cliGrep = grep; - this._config.cliListOnly = false; - this._config.cliProjectFilter = projects?.length ? projects : undefined; - this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id); + const testIdSet = params.testIds ? new Set(params.testIds) : null; + const config = await this._loadConfig(this._configFile, overrides); + config.cliListOnly = false; + config.cliPassWithNoTests = true; + config.cliArgs = params.locations || []; + config.cliGrep = params.grep; + config.cliGrepInvert = params.grepInvert; + config.cliProjectFilter = params.projects?.length ? params.projects : undefined; + config.testIdMatcher = testIdSet ? id => testIdSet.has(id) : undefined; - const reporters = await createReporters(this._config, 'ui'); - reporters.push(await createReporterForTestServer(this._config, params.reporter || require.resolve('./uiModeReporter'), 'list', e => this._dispatchEvent('report', e))); + const reporters = await createReporters(config, 'test', true); + reporters.push(await createReporterForTestServer(config, 'test', params.serializer || require.resolve('./uiModeReporter'), e => this._dispatchEvent('report', e))); const reporter = new InternalReporter(new Multiplexer(reporters)); - const taskRunner = createTaskRunnerForWatch(this._config, reporter); - const testRun = new TestRun(this._config, reporter); - reporter.onConfigure(this._config.config); + const taskRunner = createTaskRunnerForWatch(config, reporter); + const testRun = new TestRun(config, reporter); + reporter.onConfigure(config.config); const stop = new ManualPromise(); const run = taskRunner.run(testRun, 0, stop).then(async status => { await reporter.onEnd({ status }); await reporter.onExit(); this._testRun = undefined; - this._config.testIdMatcher = undefined; return status; }); this._testRun = { run, stop }; - return await run; + const status = await run; + return { status }; } async watch(params: { fileNames: string[]; }) { @@ -260,8 +287,9 @@ class TestServerDispatcher implements TestServerInterface { this._testWatcher.update([...files], [], true); } - findRelatedTestFiles(params: { files: string[]; }): Promise<{ testFiles: string[]; errors?: reporterTypes.TestError[] | undefined; }> { - const runner = new Runner(this._config); + async findRelatedTestFiles(params: Parameters[0]): ReturnType { + const config = await this._loadConfig(this._configFile); + const runner = new Runner(config); return runner.findRelatedTestFiles('out-of-process', params.files); } @@ -273,10 +301,49 @@ class TestServerDispatcher implements TestServerInterface { async closeGracefully() { gracefullyProcessExitDoNotHang(0); } + + private async _loadConfig(configFile: string | undefined, overrides?: ConfigCLIOverrides): Promise { + const configFileOrDirectory = configFile ? path.resolve(process.cwd(), configFile) : process.cwd(); + const resolvedConfigFile = resolveConfigFile(configFileOrDirectory); + const config = await loadConfig({ resolvedConfigFile, configDir: resolvedConfigFile === configFileOrDirectory ? path.dirname(resolvedConfigFile) : configFileOrDirectory }, overrides); + + // Preserve plugin instances between setup and build. + if (!this._plugins) + this._plugins = config.plugins || []; + else + config.plugins.splice(0, config.plugins.length, ...this._plugins); + return config; + } } -export async function runTestServer(config: FullConfigInternal, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise) => Promise): Promise { - const testServer = new TestServer(config); +export async function runUIMode(configFile: string | undefined, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise { + return await innerRunTestServer(configFile, options, async (server: HttpServer, cancelPromise: ManualPromise) => { + await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' }); + if (options.host !== undefined || options.port !== undefined) { + await openTraceInBrowser(server.urlPrefix()); + } else { + const page = await openTraceViewerApp(server.urlPrefix(), 'chromium', { + headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== '1', + persistentContextOptions: { + handleSIGINT: false, + }, + }); + page.on('close', () => cancelPromise.resolve()); + } + }); +} + +export async function runTestServer(configFile: string | undefined, options: { host?: string, port?: number }): Promise { + return await innerRunTestServer(configFile, options, async server => { + // eslint-disable-next-line no-console + console.log('Listening on ' + server.urlPrefix().replace('http:', 'ws:') + '/' + server.wsGuid()); + }); +} + +async function innerRunTestServer(configFile: string | undefined, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise) => Promise): Promise { + if (restartWithExperimentalTsEsm(undefined, true)) + return 'passed'; + const testServer = new TestServer(configFile); const cancelPromise = new ManualPromise(); const sigintWatcher = new SigIntWatcher(); void sigintWatcher.promise().then(() => cancelPromise.resolve()); diff --git a/packages/trace-viewer/src/ui/uiModeView.tsx b/packages/trace-viewer/src/ui/uiModeView.tsx index 06bb83bc45..fe9d7005cb 100644 --- a/packages/trace-viewer/src/ui/uiModeView.tsx +++ b/packages/trace-viewer/src/ui/uiModeView.tsx @@ -48,6 +48,21 @@ const xtermDataSource: XtermDataSource = { resize: () => {}, }; +const searchParams = new URLSearchParams(window.location.search); +const guid = searchParams.get('ws'); +const wsURL = new URL(`../${guid}`, window.location.toString()); +wsURL.protocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:'); +const queryParams = { + args: searchParams.getAll('arg'), + grep: searchParams.get('grep') || undefined, + grepInvert: searchParams.get('grepInvert') || undefined, + projects: searchParams.getAll('project'), + workers: searchParams.get('workers') || undefined, + timeout: searchParams.has('timeout') ? +searchParams.get('timeout')! : undefined, + headed: searchParams.has('headed'), + reporters: searchParams.has('reporter') ? searchParams.getAll('reporter') : undefined, +}; + export const UIModeView: React.FC<{}> = ({ }) => { const [filterText, setFilterText] = React.useState(''); @@ -76,9 +91,6 @@ export const UIModeView: React.FC<{}> = ({ const inputRef = React.useRef(null); const reloadTests = React.useCallback(() => { - const guid = new URLSearchParams(window.location.search).get('ws'); - const wsURL = new URL(`../${guid}`, window.location.toString()); - wsURL.protocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:'); setTestServerConnection(new TestServerConnection(wsURL.toString())); }, []); @@ -143,7 +155,7 @@ export const UIModeView: React.FC<{}> = ({ commandQueue.current = commandQueue.current.then(async () => { setIsLoading(true); try { - const result = await testServerConnection.listTests({}); + const result = await testServerConnection.listTests({ projects: queryParams.projects, locations: queryParams.args }); teleSuiteUpdater.processListReport(result.report); } catch (e) { // eslint-disable-next-line no-console @@ -158,10 +170,10 @@ export const UIModeView: React.FC<{}> = ({ setIsLoading(true); setWatchedTreeIds({ value: new Set() }); (async () => { - const status = await testServerConnection.runGlobalSetup(); + const status = await testServerConnection.runGlobalSetup({}); if (status !== 'passed') return; - const result = await testServerConnection.listTests({}); + const result = await testServerConnection.listTests({ projects: queryParams.projects, locations: queryParams.args }); teleSuiteUpdater.processListReport(result.report); testServerConnection.onListChanged(updateList); @@ -170,7 +182,7 @@ export const UIModeView: React.FC<{}> = ({ }); setIsLoading(false); - const { hasBrowsers } = await testServerConnection.checkBrowsers(); + const { hasBrowsers } = await testServerConnection.checkBrowsers({}); setHasBrowsers(hasBrowsers); })(); return () => { @@ -251,7 +263,18 @@ export const UIModeView: React.FC<{}> = ({ setProgress({ total: 0, passed: 0, failed: 0, skipped: 0 }); setRunningState({ testIds }); - await testServerConnection.runTests({ testIds: [...testIds], projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p) }); + await testServerConnection.runTests({ + locations: queryParams.args, + grep: queryParams.grep, + grepInvert: queryParams.grepInvert, + testIds: [...testIds], + projects: [...projectFilters].filter(([_, v]) => v).map(([p]) => p), + workers: queryParams.workers, + timeout: queryParams.timeout, + headed: queryParams.headed, + reporters: queryParams.reporters, + trace: 'on', + }); // Clear pending tests in case of interrupt. for (const test of testModel.rootSuite?.allTests() || []) { if (test.results[0]?.duration === -1) @@ -298,7 +321,7 @@ export const UIModeView: React.FC<{}> = ({ const onShortcutEvent = (e: KeyboardEvent) => { if (e.code === 'F6') { e.preventDefault(); - testServerConnection?.stopTestsNoReply(); + testServerConnection?.stopTestsNoReply({}); } else if (e.code === 'F5') { e.preventDefault(); reloadTests(); @@ -325,9 +348,9 @@ export const UIModeView: React.FC<{}> = ({ const installBrowsers = React.useCallback((e: React.MouseEvent) => { closeInstallDialog(e); setIsShowingOutput(true); - testServerConnection?.installBrowsers().then(async () => { + testServerConnection?.installBrowsers({}).then(async () => { setIsShowingOutput(false); - const { hasBrowsers } = await testServerConnection?.checkBrowsers(); + const { hasBrowsers } = await testServerConnection?.checkBrowsers({}); setHasBrowsers(hasBrowsers); }); }, [closeInstallDialog, testServerConnection]); @@ -390,7 +413,7 @@ export const UIModeView: React.FC<{}> = ({
Running {progress.passed}/{runningState.testIds.size} passed ({(progress.passed / runningState.testIds.size) * 100 | 0}%)
} runTests('bounce-if-busy', visibleTestIds)} disabled={isRunningTest || isLoading}> - testServerConnection?.stopTests()} disabled={!isRunningTest || isLoading}> + testServerConnection?.stopTests({})} disabled={!isRunningTest || isLoading}> { setWatchedTreeIds({ value: new Set() }); setWatchAll(!watchAll); From 1539cde0343a19bfa590478cb13bb9aeff905180 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 22 Mar 2024 16:04:59 -0700 Subject: [PATCH 11/29] chore: allow passing path to config to the test server (#30068) --- packages/playwright/src/program.ts | 1 + packages/playwright/src/runner/testServer.ts | 8 ++++---- tests/playwright-test/ui-mode-test-setup.spec.ts | 7 +++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/playwright/src/program.ts b/packages/playwright/src/program.ts index d16a47d4e6..dbae2f693b 100644 --- a/packages/playwright/src/program.ts +++ b/packages/playwright/src/program.ts @@ -108,6 +108,7 @@ function addFindRelatedTestFilesCommand(program: Command) { function addTestServerCommand(program: Command) { const command = program.command('test-server', { hidden: true }); command.description('start test server'); + command.option('-c, --config ', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.option('--host ', 'Host to start the server on', 'localhost'); command.option('--port ', 'Port to start the server on', '0'); command.action(opts => runTestServer(opts)); diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index 33cc6e4f9e..d100e04983 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -24,7 +24,7 @@ import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/ import type { FullConfigInternal } from '../common/config'; import { InternalReporter } from '../reporters/internalReporter'; import { createReporterForTestServer, createReporters } from './reporters'; -import { TestRun, createTaskRunnerForList, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks'; +import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup } from './tasks'; import { open } from 'playwright-core/lib/utilsBundle'; import ListReporter from '../reporters/list'; import { Multiplexer } from '../reporters/multiplexer'; @@ -97,7 +97,7 @@ class TestServerDispatcher implements TestServerInterface { this._configFile = configFile; this.transport = { dispatch: (method, params) => (this as any)[method](params), - onclose: () => {}, + onclose: () => gracefullyProcessExitDoNotHang(0), }; this._globalWatcher = new Watcher('deep', () => this._dispatchEvent('listChanged', {})); this._testWatcher = new Watcher('flat', events => { @@ -238,7 +238,7 @@ class TestServerDispatcher implements TestServerInterface { timeout: params.timeout, reporter: params.reporters ? params.reporters.map(r => [r]) : undefined, use: { - trace: params.trace === 'on' ? { mode: 'on', sources: false, _live: true } : undefined, + trace: params.trace === 'on' ? { mode: 'on', sources: false, _live: true } : (params.trace === 'off' ? 'off' : undefined), headless: params.headed ? false : undefined, _optionContextReuseMode: params.reuseContext ? 'when-possible' : undefined, _optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : undefined, @@ -263,7 +263,7 @@ class TestServerDispatcher implements TestServerInterface { const reporters = await createReporters(config, 'test', true); reporters.push(await createReporterForTestServer(config, 'test', params.serializer || require.resolve('./uiModeReporter'), e => this._dispatchEvent('report', e))); const reporter = new InternalReporter(new Multiplexer(reporters)); - const taskRunner = createTaskRunnerForWatch(config, reporter); + const taskRunner = createTaskRunnerForTestServer(config, reporter); const testRun = new TestRun(config, reporter); reporter.onConfigure(config.config); const stop = new ManualPromise(); diff --git a/tests/playwright-test/ui-mode-test-setup.spec.ts b/tests/playwright-test/ui-mode-test-setup.spec.ts index 4def43d078..7a5ce3f084 100644 --- a/tests/playwright-test/ui-mode-test-setup.spec.ts +++ b/tests/playwright-test/ui-mode-test-setup.spec.ts @@ -50,8 +50,10 @@ test('should run global setup and teardown', async ({ runUITest }) => { ]); }); -test('should teardown on sigint', async ({ runUITest }) => { +test('should teardown on sigint', async ({ runUITest, nodeVersion }) => { test.skip(process.platform === 'win32', 'No sending SIGINT on Windows'); + test.skip(nodeVersion.major < 18); + const { page, testProcess } = await runUITest({ 'playwright.config.ts': ` import { defineConfig } from '@playwright/test'; @@ -201,8 +203,9 @@ test('should run part of the setup only', async ({ runUITest }) => { for (const useWeb of [true, false]) { test.describe(`web-mode: ${useWeb}`, () => { - test('should run teardown with SIGINT', async ({ runUITest }) => { + test('should run teardown with SIGINT', async ({ runUITest, nodeVersion }) => { test.skip(process.platform === 'win32', 'No sending SIGINT on Windows'); + test.skip(nodeVersion.major < 18); const { page, testProcess } = await runUITest({ 'playwright.config.ts': ` import { defineConfig } from '@playwright/test'; From 7a3c00294457f17f568d992b8993f1332fb3a792 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 25 Mar 2024 07:41:54 -0700 Subject: [PATCH 12/29] fix(ct): allow importing json files (#30067) While we are not fully resolving imports during compilation, do not treat `json` as a non-importable asset. Fixes #29926. --- .../playwright-ct-core/src/tsxTransform.ts | 5 +---- .../playwright.ct-build.spec.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/playwright-ct-core/src/tsxTransform.ts b/packages/playwright-ct-core/src/tsxTransform.ts index ab33c114ba..a01bfdfa63 100644 --- a/packages/playwright-ct-core/src/tsxTransform.ts +++ b/packages/playwright-ct-core/src/tsxTransform.ts @@ -193,7 +193,4 @@ const artifactExtensions = new Set([ '.ttf', '.otf', '.eot', - - // Other assets - '.json', -]); \ No newline at end of file +]); diff --git a/tests/playwright-test/playwright.ct-build.spec.ts b/tests/playwright-test/playwright.ct-build.spec.ts index 38bae33604..aa053a6d8b 100644 --- a/tests/playwright-test/playwright.ct-build.spec.ts +++ b/tests/playwright-test/playwright.ct-build.spec.ts @@ -497,6 +497,24 @@ test('should render component via re-export', async ({ runInlineTest }, testInfo expect(result.passed).toBe(1); }); +test('should import json', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': playwrightCtConfigText, + 'playwright/index.html': ``, + 'playwright/index.ts': ``, + 'src/some.json': `{ "some": "value" }`, + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import json from './some.json'; + test('pass', async ({}) => { + expect(json.some).toBe('value'); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +}); + test('should render component exported via fixture', async ({ runInlineTest }, testInfo) => { const result = await runInlineTest({ 'playwright.config.ts': playwrightCtConfigText, From 95d649b406006f950f9a265ec061fdcf435d10a2 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 25 Mar 2024 07:42:13 -0700 Subject: [PATCH 13/29] feat: address api review feedback for 1.43 (#30066) - Update docs for `trace: retain-on-first-failure`. - Update docs for `devtools` option. - Rename `Locator.enterFrame()` to `Locator.contentFrame()`. - Rename `FrameLocator.exitFrame()` to `FrameLocator.owner()`. --- docs/src/api/class-framelocator.md | 98 +++++++++-------- docs/src/api/class-locator.md | 14 ++- docs/src/api/params.md | 5 +- docs/src/test-api/class-testoptions.md | 6 +- .../playwright-core/src/client/locator.ts | 4 +- packages/playwright-core/types/types.d.ts | 103 ++++++++++-------- packages/playwright/types/test.d.ts | 7 +- tests/page/locator-frame.spec.ts | 8 +- 8 files changed, 130 insertions(+), 115 deletions(-) diff --git a/docs/src/api/class-framelocator.md b/docs/src/api/class-framelocator.md index cf75fc8422..320ad36ba2 100644 --- a/docs/src/api/class-framelocator.md +++ b/docs/src/api/class-framelocator.md @@ -74,57 +74,11 @@ await page.FrameLocator(".result-frame").First.getByRole(AriaRole.Button).ClickA **Converting Locator to FrameLocator** -If you have a [Locator] object pointing to an `iframe` it can be converted to [FrameLocator] using [`method: Locator.enterFrame`]. +If you have a [Locator] object pointing to an `iframe` it can be converted to [FrameLocator] using [`method: Locator.contentFrame`]. **Converting FrameLocator to Locator** -If you have a [FrameLocator] object it can be converted to [Locator] pointing to the same `iframe` using [`method: FrameLocator.exitFrame`]. - - -## method: FrameLocator.exitFrame -* since: v1.43 -- returns: <[Locator]> - -Returns a [Locator] object pointing to the same `iframe` as this frame locator. - -Useful when you have a [FrameLocator] object obtained somewhere, and later on would like to interact with the `iframe` element. - -**Usage** - -```js -const frameLocator = page.frameLocator('iframe[name="embedded"]'); -// ... -const locator = frameLocator.exitFrame(); -await expect(locator).toBeVisible(); -``` - -```java -FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]"); -// ... -Locator locator = frameLocator.exitFrame(); -assertThat(locator).isVisible(); -``` - -```python async -frame_locator = page.frame_locator("iframe[name=\"embedded\"]") -# ... -locator = frame_locator.exit_frame -await expect(locator).to_be_visible() -``` - -```python sync -frame_locator = page.frame_locator("iframe[name=\"embedded\"]") -# ... -locator = frame_locator.exit_frame -expect(locator).to_be_visible() -``` - -```csharp -var frameLocator = Page.FrameLocator("iframe[name=\"embedded\"]"); -// ... -var locator = frameLocator.ExitFrame; -await Expect(locator).ToBeVisibleAsync(); -``` +If you have a [FrameLocator] object it can be converted to [Locator] pointing to the same `iframe` using [`method: FrameLocator.owner`]. ## method: FrameLocator.first @@ -248,3 +202,51 @@ Returns locator to the n-th matching frame. It's zero based, `nth(0)` selects th ### param: FrameLocator.nth.index * since: v1.17 - `index` <[int]> + +## method: FrameLocator.owner +* since: v1.43 +- returns: <[Locator]> + +Returns a [Locator] object pointing to the same `iframe` as this frame locator. + +Useful when you have a [FrameLocator] object obtained somewhere, and later on would like to interact with the `iframe` element. + +For a reverse operation, use [`method: Locator.contentFrame`]. + +**Usage** + +```js +const frameLocator = page.frameLocator('iframe[name="embedded"]'); +// ... +const locator = frameLocator.owner(); +await expect(locator).toBeVisible(); +``` + +```java +FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]"); +// ... +Locator locator = frameLocator.owner(); +assertThat(locator).isVisible(); +``` + +```python async +frame_locator = page.frame_locator("iframe[name=\"embedded\"]") +# ... +locator = frame_locator.owner +await expect(locator).to_be_visible() +``` + +```python sync +frame_locator = page.frame_locator("iframe[name=\"embedded\"]") +# ... +locator = frame_locator.owner +expect(locator).to_be_visible() +``` + +```csharp +var frameLocator = Page.FrameLocator("iframe[name=\"embedded\"]"); +// ... +var locator = frameLocator.Owner; +await Expect(locator).ToBeVisibleAsync(); +``` + diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index c254adc1c0..50ef738c9a 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -747,7 +747,7 @@ Resolves given locator to the first matching DOM element. If there are no matchi Resolves given locator to all matching DOM elements. If there are no matching elements, returns an empty list. -## method: Locator.enterFrame +## method: Locator.contentFrame * since: v1.43 - returns: <[FrameLocator]> @@ -755,40 +755,42 @@ Returns a [FrameLocator] object pointing to the same `iframe` as this locator. Useful when you have a [Locator] object obtained somewhere, and later on would like to interact with the content inside the frame. +For a reverse operation, use [`method: FrameLocator.owner`]. + **Usage** ```js const locator = page.locator('iframe[name="embedded"]'); // ... -const frameLocator = locator.enterFrame(); +const frameLocator = locator.contentFrame(); await frameLocator.getByRole('button').click(); ``` ```java Locator locator = page.locator("iframe[name=\"embedded\"]"); // ... -FrameLocator frameLocator = locator.enterFrame(); +FrameLocator frameLocator = locator.contentFrame(); frameLocator.getByRole(AriaRole.BUTTON).click(); ``` ```python async locator = page.locator("iframe[name=\"embedded\"]") # ... -frame_locator = locator.enter_frame +frame_locator = locator.content_frame await frame_locator.get_by_role("button").click() ``` ```python sync locator = page.locator("iframe[name=\"embedded\"]") # ... -frame_locator = locator.enter_frame +frame_locator = locator.content_frame frame_locator.get_by_role("button").click() ``` ```csharp var locator = Page.Locator("iframe[name=\"embedded\"]"); // ... -var frameLocator = locator.EnterFrame; +var frameLocator = locator.ContentFrame; await frameLocator.GetByRole(AriaRole.Button).ClickAsync(); ``` diff --git a/docs/src/api/params.md b/docs/src/api/params.md index e3b2894c3c..e33cd7d664 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -999,10 +999,11 @@ disable timeout. If specified, traces are saved into this directory. ## browser-option-devtools -* deprecated: Use [debugging tools](../debug.md) instead. - `devtools` <[boolean]> -**Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the +**Deprecated, Chromium-only.** Use [debugging tools](../debug.md) instead. + +Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the [`option: headless`] option will be set `false`. ## browser-option-slowmo diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index 0fba476d73..e8cbfa373c 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -556,10 +556,10 @@ export default defineConfig({ Whether to record trace for each test. Defaults to `'off'`. * `'off'`: Do not record trace. * `'on'`: Record trace for each test. -* `'retain-on-failure'`: Record trace for each test, but remove all traces from successful test runs. * `'on-first-retry'`: Record trace only when retrying a test for the first time. -* `'on-all-retries'`: Record traces only when retrying for all retries. -* `'retain-on-first-failure'`: Record traces only when the test fails for the first time. +* `'on-all-retries'`: Record trace only when retrying a test. +* `'retain-on-failure'`: Record trace for each test. When test run passes, remove the recorded trace. +* `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retires. When test run passes, remove the recorded trace. For more control, pass an object that specifies `mode` and trace features to enable. diff --git a/packages/playwright-core/src/client/locator.ts b/packages/playwright-core/src/client/locator.ts index a24286e71e..8fd2d8e088 100644 --- a/packages/playwright-core/src/client/locator.ts +++ b/packages/playwright-core/src/client/locator.ts @@ -192,7 +192,7 @@ export class Locator implements api.Locator { return await this._frame.$$(this._selector); } - enterFrame() { + contentFrame() { return new FrameLocator(this._frame, this._selector); } @@ -408,7 +408,7 @@ export class FrameLocator implements api.FrameLocator { return this.locator(getByRoleSelector(role, options)); } - exitFrame() { + owner() { return new Locator(this._frame, this._frameSelector); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 235de894cd..3bc56b0a83 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -11238,6 +11238,27 @@ export interface Locator { trial?: boolean; }): Promise; + /** + * Returns a {@link FrameLocator} object pointing to the same `iframe` as this locator. + * + * Useful when you have a {@link Locator} object obtained somewhere, and later on would like to interact with the + * content inside the frame. + * + * For a reverse operation, use + * [frameLocator.owner()](https://playwright.dev/docs/api/class-framelocator#frame-locator-owner). + * + * **Usage** + * + * ```js + * const locator = page.locator('iframe[name="embedded"]'); + * // ... + * const frameLocator = locator.contentFrame(); + * await frameLocator.getByRole('button').click(); + * ``` + * + */ + contentFrame(): FrameLocator; + /** * Returns the number of elements matching the locator. * @@ -11462,24 +11483,6 @@ export interface Locator { */ elementHandles(): Promise>; - /** - * Returns a {@link FrameLocator} object pointing to the same `iframe` as this locator. - * - * Useful when you have a {@link Locator} object obtained somewhere, and later on would like to interact with the - * content inside the frame. - * - * **Usage** - * - * ```js - * const locator = page.locator('iframe[name="embedded"]'); - * // ... - * const frameLocator = locator.enterFrame(); - * await frameLocator.getByRole('button').click(); - * ``` - * - */ - enterFrame(): FrameLocator; - /** * Set a value to the input field. * @@ -13170,9 +13173,10 @@ export interface BrowserType { deviceScaleFactor?: number; /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - * `headless` option will be set `false`. - * @deprecated Use [debugging tools](https://playwright.dev/docs/debug) instead. + * **Deprecated, Chromium-only.** Use [debugging tools](https://playwright.dev/docs/debug) instead. + * + * Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be + * set `false`. */ devtools?: boolean; @@ -13575,9 +13579,10 @@ export interface BrowserType { chromiumSandbox?: boolean; /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - * `headless` option will be set `false`. - * @deprecated Use [debugging tools](https://playwright.dev/docs/debug) instead. + * **Deprecated, Chromium-only.** Use [debugging tools](https://playwright.dev/docs/debug) instead. + * + * Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be + * set `false`. */ devtools?: boolean; @@ -17783,32 +17788,14 @@ export interface FileChooser { * **Converting Locator to FrameLocator** * * If you have a {@link Locator} object pointing to an `iframe` it can be converted to {@link FrameLocator} using - * [locator.enterFrame()](https://playwright.dev/docs/api/class-locator#locator-enter-frame). + * [locator.contentFrame()](https://playwright.dev/docs/api/class-locator#locator-content-frame). * * **Converting FrameLocator to Locator** * * If you have a {@link FrameLocator} object it can be converted to {@link Locator} pointing to the same `iframe` - * using [frameLocator.exitFrame()](https://playwright.dev/docs/api/class-framelocator#frame-locator-exit-frame). + * using [frameLocator.owner()](https://playwright.dev/docs/api/class-framelocator#frame-locator-owner). */ export interface FrameLocator { - /** - * Returns a {@link Locator} object pointing to the same `iframe` as this frame locator. - * - * Useful when you have a {@link FrameLocator} object obtained somewhere, and later on would like to interact with the - * `iframe` element. - * - * **Usage** - * - * ```js - * const frameLocator = page.frameLocator('iframe[name="embedded"]'); - * // ... - * const locator = frameLocator.exitFrame(); - * await expect(locator).toBeVisible(); - * ``` - * - */ - exitFrame(): Locator; - /** * Returns locator to the first matching frame. */ @@ -18190,6 +18177,27 @@ export interface FrameLocator { * @param index */ nth(index: number): FrameLocator; + + /** + * Returns a {@link Locator} object pointing to the same `iframe` as this frame locator. + * + * Useful when you have a {@link FrameLocator} object obtained somewhere, and later on would like to interact with the + * `iframe` element. + * + * For a reverse operation, use + * [locator.contentFrame()](https://playwright.dev/docs/api/class-locator#locator-content-frame). + * + * **Usage** + * + * ```js + * const frameLocator = page.frameLocator('iframe[name="embedded"]'); + * // ... + * const locator = frameLocator.owner(); + * await expect(locator).toBeVisible(); + * ``` + * + */ + owner(): Locator; } /** @@ -20270,9 +20278,10 @@ export interface LaunchOptions { chromiumSandbox?: boolean; /** - * **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the - * `headless` option will be set `false`. - * @deprecated Use [debugging tools](https://playwright.dev/docs/debug) instead. + * **Deprecated, Chromium-only.** Use [debugging tools](https://playwright.dev/docs/debug) instead. + * + * Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be + * set `false`. */ devtools?: boolean; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 497b137774..044c0ba63c 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5590,10 +5590,11 @@ export interface PlaywrightWorkerOptions { * Whether to record trace for each test. Defaults to `'off'`. * - `'off'`: Do not record trace. * - `'on'`: Record trace for each test. - * - `'retain-on-failure'`: Record trace for each test, but remove all traces from successful test runs. * - `'on-first-retry'`: Record trace only when retrying a test for the first time. - * - `'on-all-retries'`: Record traces only when retrying for all retries. - * - `'retain-on-first-failure'`: Record traces only when the test fails for the first time. + * - `'on-all-retries'`: Record trace only when retrying a test. + * - `'retain-on-failure'`: Record trace for each test. When test run passes, remove the recorded trace. + * - `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retires. When test run + * passes, remove the recorded trace. * * For more control, pass an object that specifies `mode` and trace features to enable. * diff --git a/tests/page/locator-frame.spec.ts b/tests/page/locator-frame.spec.ts index 4b79039ff9..09767331bd 100644 --- a/tests/page/locator-frame.spec.ts +++ b/tests/page/locator-frame.spec.ts @@ -299,22 +299,22 @@ it('should work with COEP/COOP/CORP isolated iframe', async ({ page, server, bro expect(await page.frames()[1].evaluate(() => window['__clicked'])).toBe(true); }); -it('locator.enterFrame should work', async ({ page, server }) => { +it('locator.contentFrame should work', async ({ page, server }) => { await routeIframe(page); await page.goto(server.EMPTY_PAGE); const locator = page.locator('iframe'); - const frameLocator = locator.enterFrame(); + const frameLocator = locator.contentFrame(); const button = frameLocator.locator('button'); expect(await button.innerText()).toBe('Hello iframe'); await expect(button).toHaveText('Hello iframe'); await button.click(); }); -it('frameLocator.exitFrame should work', async ({ page, server }) => { +it('frameLocator.owner should work', async ({ page, server }) => { await routeIframe(page); await page.goto(server.EMPTY_PAGE); const frameLocator = page.frameLocator('iframe'); - const locator = frameLocator.exitFrame(); + const locator = frameLocator.owner(); await expect(locator).toBeVisible(); expect(await locator.getAttribute('name')).toBe('frame1'); }); From 82804cce645abd8a419c2273fa9734c299fef612 Mon Sep 17 00:00:00 2001 From: Shriphad Rao Date: Mon, 25 Mar 2024 21:21:09 +0530 Subject: [PATCH 14/29] fix: time in ms for skip test (#30078) Fixes https://github.com/microsoft/playwright/issues/29861 --- packages/html-reporter/src/uiUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html-reporter/src/uiUtils.ts b/packages/html-reporter/src/uiUtils.ts index 34ad57731f..00022b4204 100644 --- a/packages/html-reporter/src/uiUtils.ts +++ b/packages/html-reporter/src/uiUtils.ts @@ -19,7 +19,7 @@ export function msToString(ms: number): string { return '-'; if (ms === 0) - return '0'; + return '0ms'; if (ms < 1000) return ms.toFixed(0) + 'ms'; From 032f27ff844c3617be2fa9bfb193e8359d8dc9d7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 25 Mar 2024 19:17:05 +0100 Subject: [PATCH 15/29] docs: change if: always() to if: !cancelled() (#30102) Fixes https://github.com/microsoft/playwright/issues/29451 See https://github.com/microsoft/playwright/issues/29451#issuecomment-2008476721 for investigation. --- docs/src/ci-intro.md | 4 ++-- docs/src/test-sharding-js.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/ci-intro.md b/docs/src/ci-intro.md index f5393cfc9e..561748b6b3 100644 --- a/docs/src/ci-intro.md +++ b/docs/src/ci-intro.md @@ -65,7 +65,7 @@ jobs: - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v4 - if: always() + if: ${{ !cancelled() }} with: name: playwright-report path: playwright-report/ @@ -103,7 +103,7 @@ jobs: - name: Run your tests run: pytest --tracing=retain-on-failure - uses: actions/upload-artifact@v4 - if: always() + if: ${{ !cancelled() }} with: name: playwright-traces path: test-results/ diff --git a/docs/src/test-sharding-js.md b/docs/src/test-sharding-js.md index 6738dbeada..8067198b49 100644 --- a/docs/src/test-sharding-js.md +++ b/docs/src/test-sharding-js.md @@ -89,7 +89,7 @@ jobs: run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Upload blob report to GitHub Actions Artifacts - if: always() + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.shardIndex }} @@ -104,7 +104,7 @@ jobs: ... merge-reports: # Merge reports after playwright-tests, even if some shards have failed - if: always() + if: ${{ !cancelled() }} needs: [playwright-tests] runs-on: ubuntu-latest From f5823786fd7d0a79346fd0d0407d9ac75b2722c3 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Mon, 25 Mar 2024 19:22:15 +0100 Subject: [PATCH 16/29] docs: migrate to newer :::warning syntax (#30089) - Migrate from `:::warning` to `:::danger[Warning]`. - Migrate from `:::caution` to `:::warning`. - `:::warning` and `:::caution` were the color same before. - `:::caution` is deprecated and gets removed in v4 of Docusaurus - Also migrate to the [newer title syntax](https://github.com/facebook/docusaurus/blob/3877c1254f29a64b9f2301775d9ecc2ae5e2a740/packages/docusaurus-utils/src/markdownUtils.ts#L86-L91) --------- Signed-off-by: Max Schmitt Co-authored-by: Dmitry Gozman --- docs/src/accessibility-testing-js.md | 2 +- docs/src/api/class-elementhandle.md | 2 +- docs/src/api/class-locator.md | 26 +++++++++++------------ docs/src/api/class-page.md | 2 +- docs/src/api/params.md | 5 ++--- docs/src/handles.md | 2 +- docs/src/locators.md | 16 +++++++------- packages/playwright-core/types/types.d.ts | 21 ++++++++---------- 8 files changed, 36 insertions(+), 40 deletions(-) diff --git a/docs/src/accessibility-testing-js.md b/docs/src/accessibility-testing-js.md index d367662545..96fe1f1865 100644 --- a/docs/src/accessibility-testing-js.md +++ b/docs/src/accessibility-testing-js.md @@ -14,7 +14,7 @@ A few examples of problems this can catch include: The following examples rely on the [`@axe-core/playwright`](https://npmjs.org/@axe-core/playwright) package which adds support for running the [axe accessibility testing engine](https://www.deque.com/axe/) as part of your Playwright tests. -:::note Disclaimer +:::note[Disclaimer] Automated accessibility tests can detect some common accessibility problems such as missing or invalid properties. But many accessibility problems can only be discovered through manual testing. We recommend using a combination of automated testing, manual accessibility assessments, and inclusive user testing. For manual assessments, we recommend [Accessibility Insights for Web](https://accessibilityinsights.io/docs/web/overview/?referrer=playwright-accessibility-testing-js), a free and open source dev tool that walks you through assessing a website for [WCAG 2.1 AA](https://www.w3.org/WAI/WCAG21/quickref/?currentsidebar=%23col_customize&levels=aaa) coverage. diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index 6d727f675a..3f27509a0d 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -4,7 +4,7 @@ ElementHandle represents an in-page DOM element. ElementHandles can be created with the [`method: Page.querySelector`] method. -:::caution Discouraged +:::warning[Discouraged] The use of ElementHandle is discouraged, use [Locator] objects and web-first assertions instead. ::: diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 50ef738c9a..9ccfa6c2b1 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -53,7 +53,7 @@ foreach (var li in await page.GetByRole("listitem").AllAsync()) Returns an array of `node.innerText` values for all matching nodes. -:::caution Asserting text +:::warning[Asserting text] If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details. ::: @@ -85,7 +85,7 @@ var texts = await page.GetByRole(AriaRole.Link).AllInnerTextsAsync(); Returns an array of `node.textContent` values for all matching nodes. -:::caution Asserting text +:::warning[Asserting text] If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] to avoid flakiness. See [assertions guide](../test-assertions.md) for more details. ::: @@ -443,7 +443,7 @@ await page.Locator("canvas").ClickAsync(new() { Returns the number of elements matching the locator. -:::caution Asserting count +:::warning[Asserting count] If you need to assert the number of elements on the page, prefer [`method: LocatorAssertions.toHaveCount`] to avoid flakiness. See [assertions guide](../test-assertions.md) for more details. ::: @@ -1121,7 +1121,7 @@ await locator.ClickAsync(); Returns the matching element's attribute value. -:::caution Asserting attributes +:::warning[Asserting attributes] If you need to assert an element's attribute, prefer [`method: LocatorAssertions.toHaveAttribute`] to avoid flakiness. See [assertions guide](../test-assertions.md) for more details. ::: @@ -1293,7 +1293,7 @@ Returns the [`element.innerHTML`](https://developer.mozilla.org/en-US/docs/Web/A Returns the [`element.innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText). -:::caution Asserting text +:::warning[Asserting text] If you need to assert text on the page, prefer [`method: LocatorAssertions.toHaveText`] with [`option: useInnerText`] option to avoid flakiness. See [assertions guide](../test-assertions.md) for more details. ::: @@ -1309,7 +1309,7 @@ If you need to assert text on the page, prefer [`method: LocatorAssertions.toHav Returns the value for the matching `` or `