diff --git a/.eslintignore b/.eslintignore index 9d22f618d8..fdcc76dda0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,9 @@ test/assets/modernizr.js /packages/*/lib/ *.js /packages/playwright-core/src/generated/* +/packages/playwright-core/src/protocol/debug.ts +/packages/playwright-core/src/protocol/validator.ts +/packages/playwright-core/src/server/injected/recorder/clipPaths.ts /packages/playwright-core/src/third_party/ /packages/playwright-core/types/* /packages/playwright-ct-core/src/generated/* diff --git a/.eslintrc.js b/.eslintrc.js index a116a37036..e71a4ffd09 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -115,7 +115,7 @@ module.exports = { "@typescript-eslint/type-annotation-spacing": 2, // file whitespace - "no-multiple-empty-lines": [2, {"max": 2}], + "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 0}], "no-mixed-spaces-and-tabs": 2, "no-trailing-spaces": 2, "linebreak-style": [ process.platform === "win32" ? 0 : 2, "unix" ], @@ -123,6 +123,7 @@ module.exports = { "key-spacing": [2, { "beforeColon": false }], + "eol-last": 2, // copyright "notice/notice": [2, { diff --git a/.github/workflows/tests_bidi.yml b/.github/workflows/tests_bidi.yml index db54550a4c..6be824869a 100644 --- a/.github/workflows/tests_bidi.yml +++ b/.github/workflows/tests_bidi.yml @@ -48,6 +48,23 @@ jobs: if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: - name: csv-report + name: csv-report-${{ matrix.channel }} path: test-results/report.csv retention-days: 7 + + - name: Azure Login + if: ${{ !cancelled() && github.ref == 'refs/heads/main' }} + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_BLOB_REPORTS_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_BLOB_REPORTS_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_BLOB_REPORTS_SUBSCRIPTION_ID }} + + - name: Upload report.csv to Azure + if: ${{ !cancelled() && github.ref == 'refs/heads/main' }} + run: | + REPORT_DIR='bidi-reports' + azcopy cp "./test-results/report.csv" "https://mspwblobreport.blob.core.windows.net/\$web/$REPORT_DIR/${{ matrix.channel }}.csv" + echo "Report url: https://mspwblobreport.z1.web.core.windows.net/$REPORT_DIR/${{ matrix.channel }}.csv" + env: + AZCOPY_AUTO_LOGIN_TYPE: AZCLI diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index e93d02d9d8..38a3546e41 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -1717,16 +1717,21 @@ var banana = await page.GetByRole(AriaRole.Listitem).Nth(2); Creates a locator matching all elements that match one or both of the two locators. -Note that when both locators match something, the resulting locator will have multiple matches and violate [locator strictness](../locators.md#strictness) guidelines. +Note that when both locators match something, the resulting locator will have multiple matches, potentially causing a [locator strictness](../locators.md#strictness) violation. **Usage** Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly. +:::note +If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, +possibly throwing the ["strict mode violation" error](../locators.md#strictness). In this case, you can use [`method: Locator.first`] to only match one of them. +::: + ```js const newEmail = page.getByRole('button', { name: 'New' }); const dialog = page.getByText('Confirm security settings'); -await expect(newEmail.or(dialog)).toBeVisible(); +await expect(newEmail.or(dialog).first()).toBeVisible(); if (await dialog.isVisible()) await page.getByRole('button', { name: 'Dismiss' }).click(); await newEmail.click(); @@ -1735,7 +1740,7 @@ await newEmail.click(); ```java Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New")); Locator dialog = page.getByText("Confirm security settings"); -assertThat(newEmail.or(dialog)).isVisible(); +assertThat(newEmail.or(dialog).first()).isVisible(); if (dialog.isVisible()) page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click(); newEmail.click(); @@ -1744,7 +1749,7 @@ newEmail.click(); ```python async new_email = page.get_by_role("button", name="New") dialog = page.get_by_text("Confirm security settings") -await expect(new_email.or_(dialog)).to_be_visible() +await expect(new_email.or_(dialog).first).to_be_visible() if (await dialog.is_visible()): await page.get_by_role("button", name="Dismiss").click() await new_email.click() @@ -1753,7 +1758,7 @@ await new_email.click() ```python sync new_email = page.get_by_role("button", name="New") dialog = page.get_by_text("Confirm security settings") -expect(new_email.or_(dialog)).to_be_visible() +expect(new_email.or_(dialog).first).to_be_visible() if (dialog.is_visible()): page.get_by_role("button", name="Dismiss").click() new_email.click() @@ -1762,7 +1767,7 @@ new_email.click() ```csharp var newEmail = page.GetByRole(AriaRole.Button, new() { Name = "New" }); var dialog = page.GetByText("Confirm security settings"); -await Expect(newEmail.Or(dialog)).ToBeVisibleAsync(); +await Expect(newEmail.Or(dialog).First).ToBeVisibleAsync(); if (await dialog.IsVisibleAsync()) await page.GetByRole(AriaRole.Button, new() { Name = "Dismiss" }).ClickAsync(); await newEmail.ClickAsync(); diff --git a/docs/src/api/params.md b/docs/src/api/params.md index e059fffe46..a1f909a4fb 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1003,7 +1003,7 @@ Additional arguments to pass to the browser instance. The list of Chromium flags Browser distribution channel. -Use "chromium" to [opt in to new headless mode](../browsers.md#opt-in-to-new-headless-mode). +Use "chromium" to [opt in to new headless mode](../browsers.md#chromium-new-headless-mode). Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to use branded [Google Chrome and Microsoft Edge](../browsers.md#google-chrome--microsoft-edge). diff --git a/docs/src/browsers.md b/docs/src/browsers.md index 90c0b2850b..1cc10d7a8d 100644 --- a/docs/src/browsers.md +++ b/docs/src/browsers.md @@ -338,11 +338,11 @@ dotnet test --settings:webkit.runsettings For Google Chrome, Microsoft Edge and other Chromium-based browsers, by default, Playwright uses open source Chromium builds. Since the Chromium project is ahead of the branded browsers, when the world is on Google Chrome N, Playwright already supports Chromium N+1 that will be released in Google Chrome and Microsoft Edge a few weeks later. -Playwright ships a regular Chromium build for headed operations and a separate [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) for headless mode. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for details. +### Chromium: headless shell -#### Optimize download size on CI +Playwright ships a regular Chromium build for headed operations and a separate [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) for headless mode. -If you are only running tests in headless shell (i.e. the `channel` option is not specified), for example on CI, you can avoid downloading the full Chromium browser by passing `--only-shell` during installation. +If you are only running tests in headless shell (i.e. the `channel` option is **not** specified), for example on CI, you can avoid downloading the full Chromium browser by passing `--only-shell` during installation. ```bash js # only running tests headlessly @@ -364,7 +364,7 @@ playwright install --with-deps --only-shell pwsh bin/Debug/netX/playwright.ps1 install --with-deps --only-shell ``` -#### Opt-in to new headless mode +### Chromium: new headless mode You can opt into the new headless mode by using `'chromium'` channel. As [official Chrome documentation puts it](https://developer.chrome.com/blog/chrome-headless-shell): @@ -419,6 +419,28 @@ pytest test_login.py --browser-channel chromium dotnet test -- Playwright.BrowserName=chromium Playwright.LaunchOptions.Channel=chromium ``` +With the new headless mode, you can skip downloading the headless shell during browser installation by using the `--no-shell` option: + +```bash js +# only running tests headlessly +npx playwright install --with-deps --no-shell +``` + +```bash java +# only running tests headlessly +mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps --no-shell" +``` + +```bash python +# only running tests headlessly +playwright install --with-deps --no-shell +``` + +```bash csharp +# only running tests headlessly +pwsh bin/Debug/netX/playwright.ps1 install --with-deps --no-shell +``` + ### Google Chrome & Microsoft Edge While Playwright can download and use the recent Chromium build, it can operate against the branded Google Chrome and Microsoft Edge browsers available on the machine (note that Playwright doesn't install them by default). In particular, the current Playwright version will support Stable and Beta channels of these browsers. diff --git a/docs/src/chrome-extensions-js-python.md b/docs/src/chrome-extensions-js-python.md index 1142e9b3a7..edbe7c06c9 100644 --- a/docs/src/chrome-extensions-js-python.md +++ b/docs/src/chrome-extensions-js-python.md @@ -214,7 +214,7 @@ def test_popup_page(page: Page, extension_id: str) -> None: ## Headless mode -By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using [channel `chromium`](./browsers.md#opt-in-to-new-headless-mode): +By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using [channel `chromium`](./browsers.md#chromium-new-headless-mode): ```js title="fixtures.ts" // ... diff --git a/packages/html-reporter/bundle.ts b/packages/html-reporter/bundle.ts index 4c6bc02632..63530cfaad 100644 --- a/packages/html-reporter/bundle.ts +++ b/packages/html-reporter/bundle.ts @@ -51,4 +51,4 @@ export function bundle(): Plugin { } }, }; -} \ No newline at end of file +} diff --git a/packages/html-reporter/src/utils.ts b/packages/html-reporter/src/utils.ts index 65404b2fe7..eec765969b 100644 --- a/packages/html-reporter/src/utils.ts +++ b/packages/html-reporter/src/utils.ts @@ -47,4 +47,3 @@ export function hashStringToInt(str: string) { hash = str.charCodeAt(i) + ((hash << 8) - hash); return Math.abs(hash % 6); } - diff --git a/packages/playwright-core/src/common/types.ts b/packages/playwright-core/src/common/types.ts index 55145b5565..f71d4237cd 100644 --- a/packages/playwright-core/src/common/types.ts +++ b/packages/playwright-core/src/common/types.ts @@ -20,4 +20,4 @@ export type Rect = Size & Point; export type Quad = [ Point, Point, Point, Point ]; export type TimeoutOptions = { timeout?: number }; export type NameValue = { name: string, value: string }; -export type HeadersArray = NameValue[]; \ No newline at end of file +export type HeadersArray = NameValue[]; diff --git a/packages/playwright-core/src/image_tools/stats.ts b/packages/playwright-core/src/image_tools/stats.ts index b35371b4a1..79b170243c 100644 --- a/packages/playwright-core/src/image_tools/stats.ts +++ b/packages/playwright-core/src/image_tools/stats.ts @@ -124,4 +124,3 @@ export class FastStats implements Stats { return (this._sum(this._partialSumMult, x1, y1, x2, y2) - this._sum(this._partialSumC1, x1, y1, x2, y2) * this._sum(this._partialSumC2, x1, y1, x2, y2) / N) / N; } } - diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index 1af083916c..b6303935b7 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -524,5 +524,3 @@ class ClankBrowserProcess implements BrowserProcess { await this._browser.close(); } } - - diff --git a/packages/playwright-core/src/server/fileUploadUtils.ts b/packages/playwright-core/src/server/fileUploadUtils.ts index 22ac13b127..2696ba0116 100644 --- a/packages/playwright-core/src/server/fileUploadUtils.ts +++ b/packages/playwright-core/src/server/fileUploadUtils.ts @@ -77,4 +77,4 @@ export async function prepareFilesForUpload(frame: Frame, params: channels.Eleme })); return { localPaths, localDirectory, filePayloads }; -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 92998a7946..1d1c60e6ce 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -436,4 +436,3 @@ function toJugglerProxyOptions(proxy: types.ProxySettings) { // Prefs for quick fixes that didn't make it to the build. // Should all be moved to `playwright.cfg`. const kBandaidFirefoxUserPrefs = {}; - diff --git a/packages/playwright-core/src/server/firefox/firefox.ts b/packages/playwright-core/src/server/firefox/firefox.ts index 9fbc409a56..1e0e4cc055 100644 --- a/packages/playwright-core/src/server/firefox/firefox.ts +++ b/packages/playwright-core/src/server/firefox/firefox.ts @@ -101,4 +101,3 @@ class JugglerReadyState extends BrowserReadyState { this._wsEndpoint.resolve(undefined); } } - diff --git a/packages/playwright-core/src/server/registry/nativeDeps.ts b/packages/playwright-core/src/server/registry/nativeDeps.ts index 6e25e3a14f..9653210a45 100644 --- a/packages/playwright-core/src/server/registry/nativeDeps.ts +++ b/packages/playwright-core/src/server/registry/nativeDeps.ts @@ -1104,4 +1104,3 @@ deps['debian12-arm64'] = { ...deps['debian12-x64'].lib2package, }, }; - diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 4e850f4a84..2517ea24ce 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -354,4 +354,4 @@ export function rewriteOpenSSLErrorIfNeeded(error: Error): Error { 'For more details, see https://github.com/openssl/openssl/blob/master/README-PROVIDERS.md#the-legacy-provider', 'You could probably modernize the certificate by following the steps at https://github.com/nodejs/node/issues/40672#issuecomment-1243648223', ].join('\n')); -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/server/socksInterceptor.ts b/packages/playwright-core/src/server/socksInterceptor.ts index 498e8bfe73..6b29636a00 100644 --- a/packages/playwright-core/src/server/socksInterceptor.ts +++ b/packages/playwright-core/src/server/socksInterceptor.ts @@ -83,4 +83,3 @@ export class SocksInterceptor { function tChannelForSocks(names: '*' | string[], arg: any, path: string, context: ValidatorContext) { throw new ValidationError(`${path}: channels are not expected in SocksSupport`); } - diff --git a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts index b8af1b9ca3..6d7459c978 100644 --- a/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts +++ b/packages/playwright-core/src/server/webkit/wkProvisionalPage.ts @@ -105,4 +105,4 @@ export class WKProvisionalPage { assert(!frameTree.frame.parentId); this._mainFrameId = frameTree.frame.id; } -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/utils/isomorphic/mimeType.ts b/packages/playwright-core/src/utils/isomorphic/mimeType.ts index 2f8b9d4829..407d935281 100644 --- a/packages/playwright-core/src/utils/isomorphic/mimeType.ts +++ b/packages/playwright-core/src/utils/isomorphic/mimeType.ts @@ -20,4 +20,4 @@ export function isJsonMimeType(mimeType: string) { export function isTextualMimeType(mimeType: string) { return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/); -} \ No newline at end of file +} diff --git a/packages/playwright-core/src/utils/linuxUtils.ts b/packages/playwright-core/src/utils/linuxUtils.ts index d8e227f107..5d98c823a5 100644 --- a/packages/playwright-core/src/utils/linuxUtils.ts +++ b/packages/playwright-core/src/utils/linuxUtils.ts @@ -77,4 +77,3 @@ function parseOSReleaseText(osReleaseText: string): Map { } return fields; } - diff --git a/packages/playwright-core/src/utils/sequence.ts b/packages/playwright-core/src/utils/sequence.ts index 27756fabeb..b063e5c488 100644 --- a/packages/playwright-core/src/utils/sequence.ts +++ b/packages/playwright-core/src/utils/sequence.ts @@ -63,4 +63,4 @@ export function findRepeatedSubsequences(s: string[]): { sequence: string[]; cou } return result; -} \ No newline at end of file +} diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index ba83204e7e..78c1c668c4 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13853,18 +13853,22 @@ export interface Locator { /** * Creates a locator matching all elements that match one or both of the two locators. * - * Note that when both locators match something, the resulting locator will have multiple matches and violate - * [locator strictness](https://playwright.dev/docs/locators#strictness) guidelines. + * Note that when both locators match something, the resulting locator will have multiple matches, potentially causing + * a [locator strictness](https://playwright.dev/docs/locators#strictness) violation. * * **Usage** * * Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog * shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly. * + * **NOTE** If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, + * possibly throwing the ["strict mode violation" error](https://playwright.dev/docs/locators#strictness). In this case, you can use + * [locator.first()](https://playwright.dev/docs/api/class-locator#locator-first) to only match one of them. + * * ```js * const newEmail = page.getByRole('button', { name: 'New' }); * const dialog = page.getByText('Confirm security settings'); - * await expect(newEmail.or(dialog)).toBeVisible(); + * await expect(newEmail.or(dialog).first()).toBeVisible(); * if (await dialog.isVisible()) * await page.getByRole('button', { name: 'Dismiss' }).click(); * await newEmail.click(); @@ -14716,7 +14720,7 @@ export interface BrowserType { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). @@ -15215,7 +15219,7 @@ export interface BrowserType { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). @@ -21566,7 +21570,7 @@ export interface LaunchOptions { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). diff --git a/packages/playwright/jsx-runtime.mjs b/packages/playwright/jsx-runtime.mjs index 742708825e..1dfd5835aa 100644 --- a/packages/playwright/jsx-runtime.mjs +++ b/packages/playwright/jsx-runtime.mjs @@ -18,4 +18,4 @@ import jsxRuntime from './jsx-runtime.js'; export const jsx = jsxRuntime.jsx; export const jsxs = jsxRuntime.jsxs; -export const Fragment = jsxRuntime.Fragment; \ No newline at end of file +export const Fragment = jsxRuntime.Fragment; diff --git a/packages/playwright/src/common/config.ts b/packages/playwright/src/common/config.ts index 0e8babce10..a86fbb5157 100644 --- a/packages/playwright/src/common/config.ts +++ b/packages/playwright/src/common/config.ts @@ -298,4 +298,4 @@ const configInternalSymbol = Symbol('configInternalSymbol'); export function getProjectId(project: FullProject): string { return (project as any).__projectId!; -} \ No newline at end of file +} diff --git a/packages/playwright/src/reporters/html.ts b/packages/playwright/src/reporters/html.ts index 302d532641..75d345e319 100644 --- a/packages/playwright/src/reporters/html.ts +++ b/packages/playwright/src/reporters/html.ts @@ -21,7 +21,7 @@ import path from 'path'; import type { TransformCallback } from 'stream'; import { Transform } from 'stream'; import { codeFrameColumns } from '../transform/babelBundle'; -import type { FullResult, FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic, TestError } from '../../types/testReporter'; +import type * as api from '../../types/testReporter'; import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath, toPosixPath } from 'playwright-core/lib/utils'; import { colors, formatError, formatResultFailure, stripAnsiEscapes } from './base'; import { resolveReporterOutputPath } from '../util'; @@ -56,8 +56,8 @@ type HtmlReporterOptions = { }; class HtmlReporter implements ReporterV2 { - private config!: FullConfig; - private suite!: Suite; + private config!: api.FullConfig; + private suite!: api.Suite; private _options: HtmlReporterOptions; private _outputFolder!: string; private _attachmentsBaseURL!: string; @@ -65,7 +65,7 @@ class HtmlReporter implements ReporterV2 { private _port: number | undefined; private _host: string | undefined; private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined; - private _topLevelErrors: TestError[] = []; + private _topLevelErrors: api.TestError[] = []; constructor(options: HtmlReporterOptions) { this._options = options; @@ -79,11 +79,11 @@ class HtmlReporter implements ReporterV2 { return false; } - onConfigure(config: FullConfig) { + onConfigure(config: api.FullConfig) { this.config = config; } - onBegin(suite: Suite) { + onBegin(suite: api.Suite) { const { outputFolder, open, attachmentsBaseURL, host, port } = this._resolveOptions(); this._outputFolder = outputFolder; this._open = open; @@ -125,11 +125,11 @@ class HtmlReporter implements ReporterV2 { return !!relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath); } - onError(error: TestError): void { + onError(error: api.TestError): void { this._topLevelErrors.push(error); } - async onEnd(result: FullResult) { + async onEnd(result: api.FullResult) { const projectSuites = this.suite.suites; await removeFolders([this._outputFolder]); const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL); @@ -223,14 +223,14 @@ export function startHtmlReportServer(folder: string): HttpServer { } class HtmlBuilder { - private _config: FullConfig; + private _config: api.FullConfig; private _reportFolder: string; private _stepsInFile = new MultiMap(); private _dataZipFile: ZipFile; private _hasTraces = false; private _attachmentsBaseURL: string; - constructor(config: FullConfig, outputDir: string, attachmentsBaseURL: string) { + constructor(config: api.FullConfig, outputDir: string, attachmentsBaseURL: string) { this._config = config; this._reportFolder = outputDir; fs.mkdirSync(this._reportFolder, { recursive: true }); @@ -238,7 +238,7 @@ class HtmlBuilder { this._attachmentsBaseURL = attachmentsBaseURL; } - async build(metadata: Metadata, projectSuites: Suite[], result: FullResult, topLevelErrors: TestError[]): Promise<{ ok: boolean, singleTestId: string | undefined }> { + async build(metadata: Metadata, projectSuites: api.Suite[], result: api.FullResult, topLevelErrors: api.TestError[]): Promise<{ ok: boolean, singleTestId: string | undefined }> { const data = new Map(); for (const projectSuite of projectSuites) { for (const fileSuite of projectSuite.suites) { @@ -378,7 +378,7 @@ class HtmlBuilder { this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName); } - private _processSuite(suite: Suite, projectName: string, path: string[], outTests: TestEntry[]) { + private _processSuite(suite: api.Suite, projectName: string, path: string[], outTests: TestEntry[]) { const newPath = [...path, suite.title]; suite.entries().forEach(e => { if (e.type === 'test') @@ -388,7 +388,7 @@ class HtmlBuilder { }); } - private _createTestEntry(test: TestCasePublic, projectName: string, path: string[]): TestEntry { + private _createTestEntry(test: api.TestCase, projectName: string, path: string[]): TestEntry { const duration = test.results.reduce((a, r) => a + r.duration, 0); const location = this._relativeLocation(test.location)!; path = path.slice(1).filter(path => path.length > 0); @@ -500,7 +500,7 @@ class HtmlBuilder { }).filter(Boolean) as TestAttachment[]; } - private _createTestResult(test: TestCasePublic, result: TestResultPublic): TestResult { + private _createTestResult(test: api.TestCase, result: api.TestResult): TestResult { return { duration: result.duration, startTime: result.startTime.toISOString(), @@ -531,7 +531,7 @@ class HtmlBuilder { return result; } - private _relativeLocation(location: Location | undefined): Location | undefined { + private _relativeLocation(location: api.Location | undefined): api.Location | undefined { if (!location) return undefined; const file = toPosixPath(path.relative(this._config.rootDir, location.file)); @@ -609,9 +609,9 @@ function stdioAttachment(chunk: Buffer | string, type: 'stdout' | 'stderr'): Jso }; } -type DedupedStep = { step: TestStepPublic, count: number, duration: number }; +type DedupedStep = { step: api.TestStep, count: number, duration: number }; -function dedupeSteps(steps: TestStepPublic[]) { +function dedupeSteps(steps: api.TestStep[]) { const result: DedupedStep[] = []; let lastResult = undefined; for (const step of steps) { diff --git a/packages/playwright/src/runner/vcs.ts b/packages/playwright/src/runner/vcs.ts index 6f7ed55c9a..da7c4c2cc8 100644 --- a/packages/playwright/src/runner/vcs.ts +++ b/packages/playwright/src/runner/vcs.ts @@ -54,4 +54,4 @@ export async function detectChangedTestFiles(baseCommit: string, configDir: stri const trackedFilesWithChanges = gitFileList(`diff ${baseCommit} --name-only`).map(file => path.join(gitRoot, file)); return new Set(affectedTestFiles([...untrackedFiles, ...trackedFilesWithChanges])); -} \ No newline at end of file +} diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 7d5fb0b423..b72fc52246 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -6002,7 +6002,7 @@ export interface PlaywrightWorkerOptions { /** * Browser distribution channel. * - * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#opt-in-to-new-headless-mode). + * Use "chromium" to [opt in to new headless mode](https://playwright.dev/docs/browsers#chromium-new-headless-mode). * * Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or * "msedge-canary" to use branded [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). diff --git a/packages/trace-viewer/bundle.ts b/packages/trace-viewer/bundle.ts index eaf09a6cc3..38375f8d76 100644 --- a/packages/trace-viewer/bundle.ts +++ b/packages/trace-viewer/bundle.ts @@ -28,4 +28,4 @@ export function bundle(): Plugin { }, }, }; -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/sw/traceModelBackends.ts b/packages/trace-viewer/src/sw/traceModelBackends.ts index ee694b2fba..4f8ea94c3d 100644 --- a/packages/trace-viewer/src/sw/traceModelBackends.ts +++ b/packages/trace-viewer/src/sw/traceModelBackends.ts @@ -160,4 +160,4 @@ export class TraceViewerServer { return; return response; } -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/third_party/devtools.ts b/packages/trace-viewer/src/third_party/devtools.ts index 27c520cbce..cc03330240 100644 --- a/packages/trace-viewer/src/third_party/devtools.ts +++ b/packages/trace-viewer/src/third_party/devtools.ts @@ -282,4 +282,4 @@ export async function generateFetchCall(resource: Entry, style: FetchStyle = Fet async function fetchRequestPostData(resource: Entry) { return resource.request.postData?._sha1 ? await fetch(`sha1/${resource.request.postData._sha1}`).then(r => r.text()) : resource.request.postData?.text; -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/ui/actionList.tsx b/packages/trace-viewer/src/ui/actionList.tsx index 101c532aea..1deb8ecd88 100644 --- a/packages/trace-viewer/src/ui/actionList.tsx +++ b/packages/trace-viewer/src/ui/actionList.tsx @@ -150,4 +150,4 @@ function excludeOrigin(url: string): string { } catch (error) { return url; } -} \ No newline at end of file +} diff --git a/packages/trace-viewer/src/ui/shared/dialog.tsx b/packages/trace-viewer/src/ui/shared/dialog.tsx new file mode 100644 index 0000000000..a58119eca7 --- /dev/null +++ b/packages/trace-viewer/src/ui/shared/dialog.tsx @@ -0,0 +1,145 @@ +/* + Copyright (c) Microsoft Corporation. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import * as React from 'react'; + +export interface DialogProps { + className?: string; + open: boolean; + width: number; + verticalOffset?: number; + requestClose?: () => void; + anchor?: React.RefObject; +} + +export const Dialog: React.FC> = ({ + className, + open, + width, + verticalOffset, + requestClose, + anchor, + children, +}) => { + const dialogRef = React.useRef(null); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_, setRecalculateDimensionsCount] = React.useState(0); + + let style: React.CSSProperties | undefined = undefined; + + if (anchor?.current) { + const bounds = anchor.current.getBoundingClientRect(); + + style = { + margin: 0, + top: bounds.bottom + (verticalOffset ?? 0), + left: buildTopLeftCoord(bounds, width), + width, + zIndex: 1, + }; + } + + React.useEffect(() => { + const onClick = (event: MouseEvent) => { + if (!dialogRef.current || !(event.target instanceof Node)) + return; + + if (!dialogRef.current.contains(event.target)) + requestClose?.(); + }; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') + requestClose?.(); + }; + + if (open) { + document.addEventListener('mousedown', onClick); + document.addEventListener('keydown', onKeyDown); + + return () => { + document.removeEventListener('mousedown', onClick); + document.removeEventListener('keydown', onKeyDown); + }; + } + + return () => {}; + }, [open, requestClose]); + + React.useEffect(() => { + const onResize = () => setRecalculateDimensionsCount(count => count + 1); + + window.addEventListener('resize', onResize); + + return () => { + window.removeEventListener('resize', onResize); + }; + }, []); + + return ( + open && ( + + {children} + + ) + ); +}; + +const buildTopLeftCoord = (bounds: DOMRect, width: number): number => { + const leftAlignCoord = buildTopLeftCoordWithAlignment(bounds, width, 'left'); + + if (leftAlignCoord.inBounds) + return leftAlignCoord.value; + + const rightAlignCoord = buildTopLeftCoordWithAlignment( + bounds, + width, + 'right' + ); + + if (rightAlignCoord.inBounds) + return rightAlignCoord.value; + + return leftAlignCoord.value; +}; + +const buildTopLeftCoordWithAlignment = ( + bounds: DOMRect, + width: number, + alignment: 'left' | 'right' +): { + value: number; + inBounds: boolean; +} => { + const maxLeft = document.documentElement.clientWidth; + + if (alignment === 'left') { + const value = bounds.left; + + return { + value, + inBounds: value + width <= maxLeft, + }; + } else { + const value = bounds.right - width; + + return { + value, + inBounds: bounds.right - width >= 0, + }; + } +}; diff --git a/packages/web/src/components/sourceChooser.tsx b/packages/web/src/components/sourceChooser.tsx index 22b91c61a5..091dce8f98 100644 --- a/packages/web/src/components/sourceChooser.tsx +++ b/packages/web/src/components/sourceChooser.tsx @@ -59,4 +59,4 @@ export function emptySource(): Source { label: '', highlight: [] }; -} \ No newline at end of file +} diff --git a/packages/web/src/components/splitView.spec.tsx b/packages/web/src/components/splitView.spec.tsx index a9260a2d48..ca11520912 100644 --- a/packages/web/src/components/splitView.spec.tsx +++ b/packages/web/src/components/splitView.spec.tsx @@ -90,4 +90,3 @@ test('drag resize', async ({ page, mount }) => { expect.soft(mainBox).toEqual({ x: 0, y: 0, width: 500, height: 100 }); expect.soft(sidebarBox).toEqual({ x: 0, y: 101, width: 500, height: 399 }); }); - diff --git a/tests/android/webview.spec.ts b/tests/android/webview.spec.ts index a0d03b69b6..fac61b5a9a 100644 --- a/tests/android/webview.spec.ts +++ b/tests/android/webview.spec.ts @@ -68,4 +68,4 @@ test('select webview from socketName', async function({ androidDevice }) { await newPage.close(); await context.close(); -}); \ No newline at end of file +}); diff --git a/tests/bidi/csvReporter.ts b/tests/bidi/csvReporter.ts index 8fb936dd11..4986a736c3 100644 --- a/tests/bidi/csvReporter.ts +++ b/tests/bidi/csvReporter.ts @@ -79,4 +79,4 @@ function csvEscape(str) { return str; } -export default CsvReporter; \ No newline at end of file +export default CsvReporter; diff --git a/tests/bidi/expectationReporter.ts b/tests/bidi/expectationReporter.ts index e136149cc4..65371165ff 100644 --- a/tests/bidi/expectationReporter.ts +++ b/tests/bidi/expectationReporter.ts @@ -86,4 +86,4 @@ function getOutcome(test: TestCase): TestExpectation { return 'unknown'; } -export default ExpectationReporter; \ No newline at end of file +export default ExpectationReporter; diff --git a/tests/expect/toThrowMatchers.test.ts b/tests/expect/toThrowMatchers.test.ts index a5d5e52ba8..bc64d13240 100644 --- a/tests/expect/toThrowMatchers.test.ts +++ b/tests/expect/toThrowMatchers.test.ts @@ -588,4 +588,4 @@ for (const toThrow of ['toThrowError', 'toThrow'] as const) { ).toThrowErrorMatchingSnapshot(); }); }); -} \ No newline at end of file +} diff --git a/tests/library/browsercontext-har.spec.ts b/tests/library/browsercontext-har.spec.ts index 4a7834013a..796a37426a 100644 --- a/tests/library/browsercontext-har.spec.ts +++ b/tests/library/browsercontext-har.spec.ts @@ -556,4 +556,4 @@ it('should ignore aborted requests', async ({ contextFactory, server }) => { const result = await Promise.race([evalPromise, page2.waitForTimeout(1000).then(() => 'timeout')]); expect(result).toBe('timeout'); } -}); \ No newline at end of file +}); diff --git a/tests/library/chromium/chromium.spec.ts b/tests/library/chromium/chromium.spec.ts index 61aa811dca..910e0830ff 100644 --- a/tests/library/chromium/chromium.spec.ts +++ b/tests/library/chromium/chromium.spec.ts @@ -629,4 +629,4 @@ test.describe('PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS=1', () => { const req = await requestPromise; expect(req.headers['x-custom-header']).toBe('custom!'); }); -}); \ No newline at end of file +}); diff --git a/tests/library/inspector/cli-codegen-test.spec.ts b/tests/library/inspector/cli-codegen-test.spec.ts index ad68e75c48..b24871877f 100644 --- a/tests/library/inspector/cli-codegen-test.spec.ts +++ b/tests/library/inspector/cli-codegen-test.spec.ts @@ -122,4 +122,4 @@ test('should generate routeFromHAR with --save-har and --save-har-glob', async ( await cli.waitForCleanExit(); const json = JSON.parse(fs.readFileSync(harFileName, 'utf-8')); expect(json.log.creator.name).toBe('Playwright'); -}); \ No newline at end of file +}); diff --git a/tests/library/popup.spec.ts b/tests/library/popup.spec.ts index 1dcede604b..8f2f1df7dc 100644 --- a/tests/library/popup.spec.ts +++ b/tests/library/popup.spec.ts @@ -288,4 +288,4 @@ async function waitForRafs(page: Page, count: number): Promise { }; window.builtinRequestAnimationFrame(onRaf); }), count); -} \ No newline at end of file +} diff --git a/tests/page/frame-evaluate.spec.ts b/tests/page/frame-evaluate.spec.ts index 16bcf6eac7..6908f03e86 100644 --- a/tests/page/frame-evaluate.spec.ts +++ b/tests/page/frame-evaluate.spec.ts @@ -180,4 +180,3 @@ it('evaluateHandle should work', async ({ page, server }) => { const windowHandle = await mainFrame.evaluateHandle(() => window); expect(windowHandle).toBeTruthy(); }); - diff --git a/tests/page/locator-misc-2.spec.ts b/tests/page/locator-misc-2.spec.ts index b32913faba..202b9a2957 100644 --- a/tests/page/locator-misc-2.spec.ts +++ b/tests/page/locator-misc-2.spec.ts @@ -173,4 +173,3 @@ it('Locator.locator() and FrameLocator.locator() should accept locator', async ( expect(await divLocator.locator('input').inputValue()).toBe('outer'); expect(await page.frameLocator('iframe').locator(divLocator).locator('input').inputValue()).toBe('inner'); }); - diff --git a/tests/page/page-event-request.spec.ts b/tests/page/page-event-request.spec.ts index 2c1d7a7eba..e1fc29eb59 100644 --- a/tests/page/page-event-request.spec.ts +++ b/tests/page/page-event-request.spec.ts @@ -272,4 +272,4 @@ it(' resource should have type image', async ({ page }) => { `) ]); expect(request.resourceType()).toBe('image'); -}); \ No newline at end of file +}); diff --git a/tests/page/page-mouse.spec.ts b/tests/page/page-mouse.spec.ts index f93ca4e2e2..7f4ce8a85d 100644 --- a/tests/page/page-mouse.spec.ts +++ b/tests/page/page-mouse.spec.ts @@ -309,4 +309,3 @@ it('should dispatch mouse move after context menu was opened', async ({ page, br } } }); - diff --git a/tests/page/page-request-fulfill.spec.ts b/tests/page/page-request-fulfill.spec.ts index 3edcd0ba0a..0d939a0a5c 100644 --- a/tests/page/page-request-fulfill.spec.ts +++ b/tests/page/page-request-fulfill.spec.ts @@ -485,4 +485,3 @@ it('should not go to the network for fulfilled requests body', { expect(body).toBeTruthy(); expect(serverHit).toBe(false); }); - diff --git a/tests/page/selectors-react.spec.ts b/tests/page/selectors-react.spec.ts index ce47f1bb25..c7bfc3f68a 100644 --- a/tests/page/selectors-react.spec.ts +++ b/tests/page/selectors-react.spec.ts @@ -176,4 +176,3 @@ for (const [name, url] of Object.entries(reacts)) { }); }); } - diff --git a/tests/page/selectors-vue.spec.ts b/tests/page/selectors-vue.spec.ts index 175e1246ee..71b60d1cb3 100644 --- a/tests/page/selectors-vue.spec.ts +++ b/tests/page/selectors-vue.spec.ts @@ -168,4 +168,3 @@ for (const [name, url] of Object.entries(vues)) { }); }); } - diff --git a/tests/playwright-test/command-line-filter.spec.ts b/tests/playwright-test/command-line-filter.spec.ts index 8937694b39..e33cb8b318 100644 --- a/tests/playwright-test/command-line-filter.spec.ts +++ b/tests/playwright-test/command-line-filter.spec.ts @@ -195,4 +195,4 @@ test('should focus a single test suite', async ({ runInlineTest }) => { expect(result.skipped).toBe(0); expect(result.report.suites[0].suites[0].suites[0].specs[0].title).toEqual('pass2'); expect(result.report.suites[0].suites[0].suites[0].specs[1].title).toEqual('pass3'); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/only-changed.spec.ts b/tests/playwright-test/only-changed.spec.ts index 5a9c6ee121..00614598de 100644 --- a/tests/playwright-test/only-changed.spec.ts +++ b/tests/playwright-test/only-changed.spec.ts @@ -427,4 +427,3 @@ test('exits successfully if there are no changes', async ({ runInlineTest, git, expect(result.exitCode).toBe(0); }); - diff --git a/tests/playwright-test/playwright.reuse.browser.spec.ts b/tests/playwright-test/playwright.reuse.browser.spec.ts index bccce95b3e..4340550f64 100644 --- a/tests/playwright-test/playwright.reuse.browser.spec.ts +++ b/tests/playwright-test/playwright.reuse.browser.spec.ts @@ -148,4 +148,4 @@ test('should produce correct test steps', async ({ runInlineTest, runServer }) = 'onStepEnd fixture: context', 'onStepEnd After Hooks' ]); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 91b32e76a2..5ddb271b70 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -2052,4 +2052,4 @@ test('project filter in report name', async ({ runInlineTest }) => { const reportFiles = await fs.promises.readdir(reportDir); expect(reportFiles.sort()).toEqual(['report-foo-b-r-6d9d49e-1.zip']); } -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/reporter-dot.spec.ts b/tests/playwright-test/reporter-dot.spec.ts index 5afda3f7bd..04a9337ec0 100644 --- a/tests/playwright-test/reporter-dot.spec.ts +++ b/tests/playwright-test/reporter-dot.spec.ts @@ -112,4 +112,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { colors.green('ยท').repeat(3)); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-github.spec.ts b/tests/playwright-test/reporter-github.spec.ts index 100feb157f..292c407b9f 100644 --- a/tests/playwright-test/reporter-github.spec.ts +++ b/tests/playwright-test/reporter-github.spec.ts @@ -98,4 +98,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(result.exitCode).toBe(1); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-junit.spec.ts b/tests/playwright-test/reporter-junit.spec.ts index 2b182e00c0..9947484903 100644 --- a/tests/playwright-test/reporter-junit.spec.ts +++ b/tests/playwright-test/reporter-junit.spec.ts @@ -594,4 +594,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(time).toBeGreaterThan(1); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-line.spec.ts b/tests/playwright-test/reporter-line.spec.ts index 22441d567a..7322af433f 100644 --- a/tests/playwright-test/reporter-line.spec.ts +++ b/tests/playwright-test/reporter-line.spec.ts @@ -189,4 +189,4 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(result.exitCode).toBe(1); }); }); -} \ No newline at end of file +} diff --git a/tests/playwright-test/reporter-list.spec.ts b/tests/playwright-test/reporter-list.spec.ts index 752d29c649..1d488cc253 100644 --- a/tests/playwright-test/reporter-list.spec.ts +++ b/tests/playwright-test/reporter-list.spec.ts @@ -319,4 +319,3 @@ function simpleAnsiRenderer(text, ttyWidth) { return screenLines.map(line => line.join('')).join('\n'); } - diff --git a/tests/playwright-test/reporter-markdown.spec.ts b/tests/playwright-test/reporter-markdown.spec.ts index 076e28d66e..558a1f8a84 100644 --- a/tests/playwright-test/reporter-markdown.spec.ts +++ b/tests/playwright-test/reporter-markdown.spec.ts @@ -157,4 +157,4 @@ test('report with worker error', async ({ runInlineTest }) => { **0 passed** :heavy_check_mark::heavy_check_mark::heavy_check_mark: `); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/snapshot-path-template.spec.ts b/tests/playwright-test/snapshot-path-template.spec.ts index 4f260469b7..2fd79403f4 100644 --- a/tests/playwright-test/snapshot-path-template.spec.ts +++ b/tests/playwright-test/snapshot-path-template.spec.ts @@ -137,4 +137,3 @@ test('arg should receive default arg', async ({ runInlineTest }, testInfo) => { expect(result.output).toContain(`A snapshot doesn't exist at ${snapshotOutputPath}, writing actual`); expect(fs.existsSync(snapshotOutputPath)).toBe(true); }); - diff --git a/tests/playwright-test/test-ignore.spec.ts b/tests/playwright-test/test-ignore.spec.ts index b552380faa..17e3b3adae 100644 --- a/tests/playwright-test/test-ignore.spec.ts +++ b/tests/playwright-test/test-ignore.spec.ts @@ -370,4 +370,4 @@ test('should always work with unix separators', async ({ runInlineTest }) => { expect(result.passed).toBe(1); expect(result.report.suites.map(s => s.file).sort()).toEqual(['a.test.ts']); expect(result.exitCode).toBe(0); -}); \ No newline at end of file +}); diff --git a/tests/playwright-test/test-use.spec.ts b/tests/playwright-test/test-use.spec.ts index 8a2bcd2401..1687cbe2e7 100644 --- a/tests/playwright-test/test-use.spec.ts +++ b/tests/playwright-test/test-use.spec.ts @@ -186,4 +186,3 @@ test('test.use() should throw if called from beforeAll ', async ({ runInlineTest expect(result.exitCode).toBe(1); expect(result.output).toContain('Playwright Test did not expect test.use() to be called here'); }); - diff --git a/tests/playwright-test/ui-mode-test-annotations.spec.ts b/tests/playwright-test/ui-mode-test-annotations.spec.ts index eeff6a5aca..cc1f0b5f04 100644 --- a/tests/playwright-test/ui-mode-test-annotations.spec.ts +++ b/tests/playwright-test/ui-mode-test-annotations.spec.ts @@ -46,4 +46,3 @@ test('should display annotations', async ({ runUITest }) => { await expect(annotations.locator('.annotation-item').filter({ hasText: 'test repo' }).locator('a')) .toHaveAttribute('href', 'https://github.com/microsoft/playwright'); }); - diff --git a/utils/doclint/linting-code-snippets/cli.js b/utils/doclint/linting-code-snippets/cli.js index 5d3200aa9e..7139bb105a 100644 --- a/utils/doclint/linting-code-snippets/cli.js +++ b/utils/doclint/linting-code-snippets/cli.js @@ -153,6 +153,7 @@ class JSLintingService extends LintingService { '@typescript-eslint/no-unused-vars': 'off', 'max-len': ['error', { code: 100 }], 'react/react-in-jsx-scope': 'off', + 'eol-last': 'off', }, } });