From 6d4f9376748605f473e0e98311eba5f5738f3c79 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 24 Aug 2023 12:48:39 -0700 Subject: [PATCH 01/22] chore: fix installation tests (#26691) --- .../src/server/registry/oopDownloadBrowserMain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts index b0332405c2477..a34a9b20c7e1e 100644 --- a/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts +++ b/packages/playwright-core/src/server/registry/oopDownloadBrowserMain.ts @@ -96,6 +96,7 @@ function downloadFile(options: DownloadParams): Promise { async function main(options: DownloadParams) { await downloadFile(options); + log(`SUCCESS downloading ${options.title}`); log(`extracting archive`); await extract(options.zipPath, { dir: options.browserDirectory }); if (options.executablePath) { From 0ecc13038f3b4adccd58d96b67ffa6773c453668 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 24 Aug 2023 12:59:42 -0700 Subject: [PATCH 02/22] fix(textContent): make it work for ShadowRoot (#26690) It used to work, but regressed in v1.36. Fixes #26636. --- .../src/server/injected/injectedScript.ts | 6 ++++++ tests/page/elementhandle-convenience.spec.ts | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index dfa15dddebae8..6bd763d43e8b1 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -208,6 +208,12 @@ export class InjectedScript { throw this.createStacklessError('Internal error: there should not be a capture in the selector.'); } + // Workaround so that ":scope" matches the ShadowRoot. + // This is, unfortunately, because an ElementHandle can point to any Node (including ShadowRoot/Document/etc), + // and not just to an Element, and we support various APIs on ElementHandle like "textContent()". + if (root.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ && selector.parts.length === 1 && selector.parts[0].name === 'css' && selector.parts[0].source === ':scope') + return [root as Element]; + this._evaluator.begin(); try { let roots = new Set([root as Element]); diff --git a/tests/page/elementhandle-convenience.spec.ts b/tests/page/elementhandle-convenience.spec.ts index e57d2552ec7e0..21ef4c85bfe1b 100644 --- a/tests/page/elementhandle-convenience.spec.ts +++ b/tests/page/elementhandle-convenience.spec.ts @@ -88,6 +88,20 @@ it('textContent should work', async ({ page, server }) => { expect(await page.textContent('#inner')).toBe('Text,\nmore text'); }); +it('textContent should work on ShadowRoot', async ({ page, server }) => { + await page.setContent(` +
+ + `); + const div = await page.$('div'); + const root = await div.evaluateHandle(div => div.shadowRoot); + expect(await root.textContent()).toBe('hello'); + // We do not match ShadowRoot as ":scope". + expect(await root.$$(':scope div')).toEqual([]); +}); + it('isVisible and isHidden should work', async ({ page }) => { await page.setContent(`
Hi
`); From c9701795518b9c68388e4fe5b9d9ca00b9c0d376 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Thu, 24 Aug 2023 14:09:00 -0700 Subject: [PATCH 03/22] fix(resolver): allow importing packages with non-index main script (#26692) Regressed in https://github.com/microsoft/playwright/pull/23254. Fixes #26650. --- packages/playwright-test/src/util.ts | 7 ++++- tests/playwright-test/resolver.spec.ts | 37 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/playwright-test/src/util.ts b/packages/playwright-test/src/util.ts index 1ef5c61452c23..f825c145c7a9a 100644 --- a/packages/playwright-test/src/util.ts +++ b/packages/playwright-test/src/util.ts @@ -342,8 +342,13 @@ export function resolveImportSpecifierExtension(resolved: string): string | unde } break; // Do not try '' when a more specific extesion like '.jsx' matched. } - // try directory imports last + if (dirExists(resolved)) { + // If we import a package, let Node.js figure out the correct import based on package.json. + if (fileExists(path.join(resolved, 'package.json'))) + return resolved; + + // Otherwise, try to find a corresponding index file. const dirImport = path.join(resolved, 'index'); return resolveImportSpecifierExtension(dirImport); } diff --git a/tests/playwright-test/resolver.spec.ts b/tests/playwright-test/resolver.spec.ts index 3101bcd7a9c92..56b3da0a56357 100644 --- a/tests/playwright-test/resolver.spec.ts +++ b/tests/playwright-test/resolver.spec.ts @@ -505,3 +505,40 @@ test('should support extends in tsconfig.json', async ({ runInlineTest }) => { expect(result.passed).toBe(1); expect(result.exitCode).toBe(0); }); + +test('should import packages with non-index main script through path resolver', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'app/pkg/main.ts': ` + export const foo = 42; + `, + 'app/pkg/package.json': ` + { "main": "main.ts" } + `, + 'package.json': ` + { "name": "example-project" } + `, + 'playwright.config.ts': ` + export default {}; + `, + 'tsconfig.json': `{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "app/*": ["app/*"], + }, + }, + }`, + 'example.spec.ts': ` + import { foo } from 'app/pkg'; + import { test, expect } from '@playwright/test'; + test('test', ({}) => { + console.log('foo=' + foo); + }); + `, + }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + expect(result.output).not.toContain(`find module`); + expect(result.output).toContain(`foo=42`); +}); From 39a6b233090a1b76554f78ab645c9d283f0d4e40 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 24 Aug 2023 16:06:41 -0700 Subject: [PATCH 04/22] devops: create blob reports for all "tests 1" (#26694) --- .github/workflows/tests_primary.yml | 34 ++++++++++++++++++++- packages/html-reporter/playwright.config.ts | 2 +- packages/web/playwright.config.ts | 13 +++++++- tests/installation/playwright.config.ts | 18 ++++++++--- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index 4fa4749af205c..3a04dc9ae2285 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -158,10 +158,26 @@ jobs: env: DEBUG: pw:install - run: npm run build + - run: npx playwright install --with-deps - run: npm run test-html-reporter + env: + PWTEST_BLOB_REPORT_NAME: "web-components-html-reporter" + - name: Upload blob report + if: always() + uses: ./.github/actions/upload-blob-report + with: + report_dir: packages/html-reporter/blob-report + - run: npm run test-web if: always() + env: + PWTEST_BLOB_REPORT_NAME: "web-components-web" + - name: Upload blob report + if: always() + uses: ./.github/actions/upload-blob-report + with: + report_dir: packages/web/blob-report test_vscode_extension: name: VSCode Extension @@ -189,9 +205,16 @@ jobs: working-directory: ./playwright-vscode - name: Run extension tests run: npm run test -- --workers=1 + env: + PWTEST_BLOB_REPORT_NAME: "vscode-extension" working-directory: ./playwright-vscode + - name: Upload blob report + if: always() + uses: ./.github/actions/upload-blob-report + with: + report_dir: ./playwright-vscode/blob-report - test-package-installations: + test_package_installations: name: "Installation Test ${{ matrix.os }}" runs-on: ${{ matrix.os }} strategy: @@ -214,8 +237,17 @@ jobs: - run: npx playwright install-deps - run: npm run itest if: matrix.os != 'ubuntu-latest' + env: + PWTEST_BLOB_REPORT_NAME: "package-installations-${{ matrix.os }}" - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run itest if: matrix.os == 'ubuntu-latest' + env: + PWTEST_BLOB_REPORT_NAME: "package-installations-${{ matrix.os }}" - run: ./utils/upload_flakiness_dashboard.sh ./test-results/report.json if: always() shell: bash + - name: Upload blob report + if: always() + uses: ./.github/actions/upload-blob-report + with: + report_dir: blob-report diff --git a/packages/html-reporter/playwright.config.ts b/packages/html-reporter/playwright.config.ts index 41140fb50cbdc..aa3c5e04948b5 100644 --- a/packages/html-reporter/playwright.config.ts +++ b/packages/html-reporter/playwright.config.ts @@ -21,7 +21,7 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, snapshotPathTemplate: '{testDir}/__screenshots__/{projectName}/{testFilePath}/{arg}{ext}', - reporter: 'html', + reporter: process.env.CI ? 'blob' : 'html', use: { ctPort: 3101, trace: 'on-first-retry', diff --git a/packages/web/playwright.config.ts b/packages/web/playwright.config.ts index 18ad0df47a748..8169cfa66114a 100644 --- a/packages/web/playwright.config.ts +++ b/packages/web/playwright.config.ts @@ -15,12 +15,23 @@ */ import { devices, defineConfig } from '@playwright/experimental-ct-react'; +import type { ReporterDescription } from '@playwright/test'; + +const reporters = () => { + const result: ReporterDescription[] = process.env.CI ? [ + ['html'], + ['blob'], + ] : [ + ['html'] + ]; + return result; +}; export default defineConfig({ testDir: 'src', forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, - reporter: 'html', + reporter: reporters(), use: { ctPort: 3102, trace: 'on-first-retry', diff --git a/tests/installation/playwright.config.ts b/tests/installation/playwright.config.ts index 882f5bfaad20a..fc3dda9023211 100644 --- a/tests/installation/playwright.config.ts +++ b/tests/installation/playwright.config.ts @@ -16,9 +16,22 @@ import path from 'path'; import { defineConfig } from '@playwright/test'; +import type { ReporterDescription } from '@playwright/test'; import { config as loadEnv } from 'dotenv'; loadEnv({ path: path.join(__dirname, '..', '..', '.env') }); +const reporters = () => { + const result: ReporterDescription[] = process.env.CI ? [ + ['dot'], + ['json', { outputFile: path.join(outputDir, 'report.json') }], + ['blob'], + ] : [ + ['list'], + ['html', { open: 'on-failure' }] + ]; + return result; +}; + const outputDir = path.join(__dirname, '..', '..', 'test-results'); export default defineConfig({ globalSetup: path.join(__dirname, 'globalSetup'), @@ -26,10 +39,7 @@ export default defineConfig({ testIgnore: '**\/fixture-scripts/**', timeout: 5 * 60 * 1000, retries: 0, - reporter: process.env.CI ? [ - ['dot'], - ['json', { outputFile: path.join(outputDir, 'report.json') }], - ] : [['list'], ['html', { open: 'on-failure' }]], + reporter: reporters(), forbidOnly: !!process.env.CI, workers: 1, projects: [ From e7bd1864a8fd6f5af0a58d03219e9eda7763e38f Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 24 Aug 2023 16:19:57 -0700 Subject: [PATCH 05/22] chore: do not add plugins to config twice (#26670) --- .../playwright-ct-core/src/tsxTransform.ts | 15 +- packages/playwright-ct-core/src/vitePlugin.ts | 133 ++++++----- .../playwright-ct-react/registerSource.mjs | 8 +- .../playwright-ct-react17/registerSource.mjs | 10 +- .../src/transform/compilationCache.ts | 7 +- .../playwright.ct-build.spec.ts | 123 +++++++++-- tests/playwright-test/ui-mode-test-ct.spec.ts | 209 ++++++++++++++++++ 7 files changed, 415 insertions(+), 90 deletions(-) create mode 100644 tests/playwright-test/ui-mode-test-ct.spec.ts diff --git a/packages/playwright-ct-core/src/tsxTransform.ts b/packages/playwright-ct-core/src/tsxTransform.ts index edbaa3e05b599..4d1d95bd67773 100644 --- a/packages/playwright-ct-core/src/tsxTransform.ts +++ b/packages/playwright-ct-core/src/tsxTransform.ts @@ -166,10 +166,11 @@ export function collectComponentUsages(node: T.Node) { } export type ComponentInfo = { - fullName: string, - importPath: string, - isModuleOrAlias: boolean, - importedName?: string + fullName: string; + importPath: string; + isModuleOrAlias: boolean; + importedName?: string; + deps: string[]; }; export function componentInfo(specifier: T.ImportSpecifier | T.ImportDefaultSpecifier, importSource: string, filename: string): ComponentInfo { @@ -183,9 +184,9 @@ export function componentInfo(specifier: T.ImportSpecifier | T.ImportDefaultSpec const pathInfo = { importPath, isModuleOrAlias }; if (t.isImportDefaultSpecifier(specifier)) - return { fullName: prefix, ...pathInfo }; + return { fullName: prefix, deps: [], ...pathInfo }; if (t.isIdentifier(specifier.imported)) - return { fullName: prefix + '_' + specifier.imported.name, importedName: specifier.imported.name, ...pathInfo }; - return { fullName: prefix + '_' + specifier.imported.value, importedName: specifier.imported.value, ...pathInfo }; + return { fullName: prefix + '_' + specifier.imported.name, importedName: specifier.imported.name, deps: [], ...pathInfo }; + return { fullName: prefix + '_' + specifier.imported.value, importedName: specifier.imported.value, deps: [], ...pathInfo }; } diff --git a/packages/playwright-ct-core/src/vitePlugin.ts b/packages/playwright-ct-core/src/vitePlugin.ts index 626659c53340a..f714564bd98e2 100644 --- a/packages/playwright-ct-core/src/vitePlugin.ts +++ b/packages/playwright-ct-core/src/vitePlugin.ts @@ -17,11 +17,12 @@ import type { Suite } from '@playwright/test/reporter'; import type { PlaywrightTestConfig as BasePlaywrightTestConfig, FullConfig } from '@playwright/test'; -import type { InlineConfig, Plugin, ResolveFn, ResolvedConfig } from 'vite'; +import type { InlineConfig, Plugin, ResolveFn, ResolvedConfig, UserConfig } from 'vite'; import type { TestRunnerPlugin } from '../../playwright-test/src/plugins'; import type { ComponentInfo } from './tsxTransform'; import type { AddressInfo } from 'net'; import type { PluginContext } from 'rollup'; +import { debug } from 'playwright-core/lib/utilsBundle'; import fs from 'fs'; import path from 'path'; @@ -31,6 +32,9 @@ import { assert, calculateSha1 } from 'playwright-core/lib/utils'; import { getPlaywrightVersion } from 'playwright-core/lib/utils'; import { setExternalDependencies } from '@playwright/test/lib/transform/compilationCache'; import { collectComponentUsages, componentInfo } from './tsxTransform'; +import { version as viteVersion, build, preview, mergeConfig } from 'vite'; + +const log = debug('pw:vite'); let stoppableServer: any; const playwrightVersion = getPlaywrightVersion(); @@ -59,23 +63,51 @@ export function createPlugin( }, begin: async (suite: Suite) => { + // We are going to have 3 config files: + // - the defaults that user config overrides (baseConfig) + // - the user config (userConfig) + // - frameworks overrides (frameworkOverrides); + const use = config.projects[0].use as CtConfig; const port = use.ctPort || 3100; - const viteConfig = typeof use.ctViteConfig === 'function' ? await use.ctViteConfig() : (use.ctViteConfig || {}); - const templateDirConfig = use.ctTemplateDir || 'playwright'; + const relativeTemplateDir = use.ctTemplateDir || 'playwright'; + + // FIXME: use build plugin to determine html location to resolve this. + // TemplateDir must be relative, otherwise we can't move the final index.html into its target location post-build. + // This regressed in https://github.com/microsoft/playwright/pull/26526 + const templateDir = path.join(configDir, relativeTemplateDir); + + // Compose base config from the playwright config only. + const baseConfig = { + root: configDir, + configFile: false, + define: { + __VUE_PROD_DEVTOOLS__: true, + }, + css: { + devSourcemap: true, + }, + build: { + outDir: use.ctCacheDir ? path.resolve(configDir, use.ctCacheDir) : path.resolve(templateDir, '.cache') + }, + preview: { + port + }, + // Vite preview server will otherwise always return the index.html with 200. + appType: 'custom', + }; - const rootDir = viteConfig.root || configDir; - const templateDir = path.resolve(rootDir, templateDirConfig); - const outDir = viteConfig?.build?.outDir || (use.ctCacheDir ? path.resolve(rootDir, use.ctCacheDir) : path.resolve(templateDir, '.cache')); + // Apply user config on top of the base config. This could have changed root and build.outDir. + const userConfig = typeof use.ctViteConfig === 'function' ? await use.ctViteConfig() : (use.ctViteConfig || {}); + const baseAndUserConfig = mergeConfig(baseConfig, userConfig); + const buildInfoFile = path.join(baseAndUserConfig.build.outDir, 'metainfo.json'); - const buildInfoFile = path.join(outDir, 'metainfo.json'); let buildExists = false; let buildInfo: BuildInfo; const registerSource = await fs.promises.readFile(registerSourceFile, 'utf-8'); const registerSourceHash = calculateSha1(registerSource); - const { version: viteVersion } = await import('vite'); try { buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo; assert(buildInfo.version === playwrightVersion); @@ -92,54 +124,52 @@ export function createPlugin( sources: {}, }; } + log('build exists:', buildExists); const componentRegistry: ComponentRegistry = new Map(); // 1. Re-parse changed tests and collect required components. const hasNewTests = await checkNewTests(suite, buildInfo, componentRegistry); + log('has new tests:', hasNewTests); + // 2. Check if the set of required components has changed. const hasNewComponents = await checkNewComponents(buildInfo, componentRegistry); + log('has new components:', hasNewComponents); + // 3. Check component sources. const sourcesDirty = !buildExists || hasNewComponents || await checkSources(buildInfo); + log('sourcesDirty:', sourcesDirty); + // 4. Update component info. buildInfo.components = [...componentRegistry.values()]; - viteConfig.root = rootDir; - viteConfig.preview = { port, ...viteConfig.preview }; - // Vite preview server will otherwise always return the index.html with 200. - viteConfig.appType = viteConfig.appType || 'custom'; + const frameworkOverrides: UserConfig = { plugins: [] }; // React heuristic. If we see a component in a file with .js extension, // consider it a potential JSX-in-JS scenario and enable JSX loader for all // .js files. if (hasJSComponents(buildInfo.components)) { - viteConfig.esbuild = { + log('jsx-in-js detected'); + frameworkOverrides.esbuild = { loader: 'jsx', include: /.*\.jsx?$/, exclude: [], }; - viteConfig.optimizeDeps = { + frameworkOverrides.optimizeDeps = { esbuildOptions: { loader: { '.js': 'jsx' }, } }; } - const { build, preview } = await import('vite'); - // Build config unconditionally, either build or build & preview will use it. - viteConfig.plugins ??= []; - if (frameworkPluginFactory && !viteConfig.plugins.length) - viteConfig.plugins = [await frameworkPluginFactory()]; + + // We assume that any non-empty plugin list includes `vite-react` or similar. + if (frameworkPluginFactory && !baseAndUserConfig.plugins?.length) + frameworkOverrides.plugins = [await frameworkPluginFactory()]; // But only add out own plugin when we actually build / transform. if (sourcesDirty) - viteConfig.plugins.push(vitePlugin(registerSource, templateDir, buildInfo, componentRegistry)); - viteConfig.configFile = viteConfig.configFile || false; - viteConfig.define = viteConfig.define || {}; - viteConfig.define.__VUE_PROD_DEVTOOLS__ = true; - viteConfig.css = viteConfig.css || {}; - viteConfig.css.devSourcemap = true; - viteConfig.build = { - ...viteConfig.build, - outDir, + frameworkOverrides.plugins!.push(vitePlugin(registerSource, templateDir, buildInfo, componentRegistry)); + + frameworkOverrides.build = { target: 'esnext', minify: false, rollupOptions: { @@ -151,24 +181,34 @@ export function createPlugin( sourcemap: true, }; + const finalConfig = mergeConfig(baseAndUserConfig, frameworkOverrides); + if (sourcesDirty) { - await build(viteConfig); - const relativeTemplateDir = path.relative(rootDir, templateDir); - await fs.promises.rename(path.resolve(outDir, relativeTemplateDir, 'index.html'), `${outDir}/index.html`); + log('build'); + await build(finalConfig); + await fs.promises.rename(`${finalConfig.build.outDir}/${relativeTemplateDir}/index.html`, `${finalConfig.build.outDir}/index.html`); } - if (hasNewTests || hasNewComponents || sourcesDirty) + if (hasNewTests || hasNewComponents || sourcesDirty) { + log('write manifest'); await fs.promises.writeFile(buildInfoFile, JSON.stringify(buildInfo, undefined, 2)); + } - for (const [filename, testInfo] of Object.entries(buildInfo.tests)) - setExternalDependencies(filename, testInfo.deps); + for (const [filename, testInfo] of Object.entries(buildInfo.tests)) { + const deps = new Set(); + for (const componentName of testInfo.components) { + const component = componentRegistry.get(componentName); + component?.deps.forEach(d => deps.add(d)); + } + setExternalDependencies(filename, [...deps]); + } - const previewServer = await preview(viteConfig); + const previewServer = await preview(finalConfig); stoppableServer = stoppable(previewServer.httpServer, 0); const isAddressInfo = (x: any): x is AddressInfo => x?.address; const address = previewServer.httpServer.address(); if (isAddressInfo(address)) { - const protocol = viteConfig.preview.https ? 'https:' : 'http:'; + const protocol = finalConfig.preview.https ? 'https:' : 'http:'; process.env.PLAYWRIGHT_TEST_BASE_URL = `${protocol}//localhost:${address.port}`; } }, @@ -194,7 +234,6 @@ type BuildInfo = { [key: string]: { timestamp: number; components: string[]; - deps: string[]; } }; }; @@ -205,9 +244,12 @@ async function checkSources(buildInfo: BuildInfo): Promise { for (const [source, sourceInfo] of Object.entries(buildInfo.sources)) { try { const timestamp = (await fs.promises.stat(source)).mtimeMs; - if (sourceInfo.timestamp !== timestamp) + if (sourceInfo.timestamp !== timestamp) { + log('source has changed:', source); return true; + } } catch (e) { + log('check source failed:', e); return true; } } @@ -226,9 +268,10 @@ async function checkNewTests(suite: Suite, buildInfo: BuildInfo, componentRegist const timestamp = (await fs.promises.stat(testFile)).mtimeMs; if (buildInfo.tests[testFile]?.timestamp !== timestamp) { const components = await parseTestFile(testFile); + log('changed test:', testFile); for (const component of components) componentRegistry.set(component.fullName, component); - buildInfo.tests[testFile] = { timestamp, components: components.map(c => c.fullName), deps: [] }; + buildInfo.tests[testFile] = { timestamp, components: components.map(c => c.fullName) }; hasNewTests = true; } } @@ -336,23 +379,13 @@ function vitePlugin(registerSource: string, templateDir: string, buildInfo: Buil }, async writeBundle(this: PluginContext) { - const componentDeps = new Map>(); for (const component of componentRegistry.values()) { const id = (await moduleResolver(component.importPath)); if (!id) continue; const deps = new Set(); collectViteModuleDependencies(this, id, deps); - componentDeps.set(component.fullName, deps); - } - - for (const testInfo of Object.values(buildInfo.tests)) { - const deps = new Set(); - for (const fullName of testInfo.components) { - for (const dep of componentDeps.get(fullName) || []) - deps.add(dep); - } - testInfo.deps = [...deps]; + component.deps = [...deps]; } }, }; diff --git a/packages/playwright-ct-react/registerSource.mjs b/packages/playwright-ct-react/registerSource.mjs index 400924f1ede83..e6380757a1418 100644 --- a/packages/playwright-ct-react/registerSource.mjs +++ b/packages/playwright-ct-react/registerSource.mjs @@ -53,7 +53,7 @@ function isComponent(component) { */ async function __pwResolveComponent(component) { if (!isComponent(component)) - return + return; let componentFactory = __pwLoaderRegistry.get(component.type); if (!componentFactory) { @@ -69,11 +69,11 @@ async function __pwResolveComponent(component) { if (!componentFactory && component.type[0].toUpperCase() === component.type[0]) throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`); - if(componentFactory) - __pwRegistry.set(component.type, await componentFactory()) + if (componentFactory) + __pwRegistry.set(component.type, await componentFactory()); if ('children' in component) - await Promise.all(component.children.map(child => __pwResolveComponent(child))) + await Promise.all(component.children.map(child => __pwResolveComponent(child))); } /** diff --git a/packages/playwright-ct-react17/registerSource.mjs b/packages/playwright-ct-react17/registerSource.mjs index 06a0ec10b1f8f..0a3bef1b5b663 100644 --- a/packages/playwright-ct-react17/registerSource.mjs +++ b/packages/playwright-ct-react17/registerSource.mjs @@ -52,7 +52,7 @@ function isComponent(component) { */ async function __pwResolveComponent(component) { if (!isComponent(component)) - return + return; let componentFactory = __pwLoaderRegistry.get(component.type); if (!componentFactory) { @@ -68,11 +68,11 @@ async function __pwResolveComponent(component) { if (!componentFactory && component.type[0].toUpperCase() === component.type[0]) throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`); - if(componentFactory) - __pwRegistry.set(component.type, await componentFactory()) + if (componentFactory) + __pwRegistry.set(component.type, await componentFactory()); if ('children' in component) - await Promise.all(component.children.map(child => __pwResolveComponent(child))) + await Promise.all(component.children.map(child => __pwResolveComponent(child))); } /** @@ -83,7 +83,7 @@ function __pwRender(component) { return component; const componentFunc = __pwRegistry.get(component.type); - + if (component.kind !== 'jsx') throw new Error('Object mount notation is not supported'); diff --git a/packages/playwright-test/src/transform/compilationCache.ts b/packages/playwright-test/src/transform/compilationCache.ts index 52e89fc3c907c..df768cfe3e438 100644 --- a/packages/playwright-test/src/transform/compilationCache.ts +++ b/packages/playwright-test/src/transform/compilationCache.ts @@ -177,7 +177,12 @@ export function collectAffectedTestFiles(dependency: string, testFileCollector: } export function dependenciesForTestFile(filename: string): Set { - return fileDependencies.get(filename) || new Set(); + const result = new Set(); + for (const dep of fileDependencies.get(filename) || []) + result.add(dep); + for (const dep of externalDependencies.get(filename) || []) + result.add(dep); + return result; } // These two are only used in the dev mode, they are specifically excluding diff --git a/tests/playwright-test/playwright.ct-build.spec.ts b/tests/playwright-test/playwright.ct-build.spec.ts index 2b341a1ad9064..866fedcab6aba 100644 --- a/tests/playwright-test/playwright.ct-build.spec.ts +++ b/tests/playwright-test/playwright.ct-build.spec.ts @@ -138,31 +138,55 @@ test('should extract component list', async ({ runInlineTest }, testInfo) => { fullName: expect.stringContaining('playwright_test_src_button_tsx_Button'), importedName: 'Button', importPath: expect.stringContaining('button.tsx'), - isModuleOrAlias: false + isModuleOrAlias: false, + deps: [ + expect.stringContaining('button.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] }, { fullName: expect.stringContaining('playwright_test_src_clashingNames1_tsx_ClashingName'), importedName: 'ClashingName', importPath: expect.stringContaining('clashingNames1.tsx'), - isModuleOrAlias: false + isModuleOrAlias: false, + deps: [ + expect.stringContaining('clashingNames1.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] }, { fullName: expect.stringContaining('playwright_test_src_clashingNames2_tsx_ClashingName'), importedName: 'ClashingName', importPath: expect.stringContaining('clashingNames2.tsx'), - isModuleOrAlias: false + isModuleOrAlias: false, + deps: [ + expect.stringContaining('clashingNames2.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] }, { fullName: expect.stringContaining('playwright_test_src_components_tsx_Component1'), importedName: 'Component1', importPath: expect.stringContaining('components.tsx'), - isModuleOrAlias: false + isModuleOrAlias: false, + deps: [ + expect.stringContaining('components.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] }, { fullName: expect.stringContaining('playwright_test_src_components_tsx_Component2'), importedName: 'Component2', importPath: expect.stringContaining('components.tsx'), - isModuleOrAlias: false + isModuleOrAlias: false, + deps: [ + expect.stringContaining('components.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] }, { fullName: expect.stringContaining('playwright_test_src_defaultExport_tsx'), importPath: expect.stringContaining('defaultExport.tsx'), - isModuleOrAlias: false + isModuleOrAlias: false, + deps: [ + expect.stringContaining('defaultExport.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] }]); for (const [file, test] of Object.entries(metainfo.tests)) { @@ -173,11 +197,6 @@ test('should extract component list', async ({ runInlineTest }, testInfo) => { expect.stringContaining('clashingNames1_tsx_ClashingName'), expect.stringContaining('clashingNames2_tsx_ClashingName'), ], - deps: [ - expect.stringContaining('clashingNames1.tsx'), - expect.stringContaining('jsx-runtime.js'), - expect.stringContaining('clashingNames2.tsx'), - ], }); } if (file.endsWith('default-import.spec.tsx')) { @@ -186,10 +205,6 @@ test('should extract component list', async ({ runInlineTest }, testInfo) => { components: [ expect.stringContaining('defaultExport_tsx'), ], - deps: [ - expect.stringContaining('defaultExport.tsx'), - expect.stringContaining('jsx-runtime.js'), - ] }); } if (file.endsWith('named-imports.spec.tsx')) { @@ -199,10 +214,6 @@ test('should extract component list', async ({ runInlineTest }, testInfo) => { expect.stringContaining('components_tsx_Component1'), expect.stringContaining('components_tsx_Component2'), ], - deps: [ - expect.stringContaining('components.tsx'), - expect.stringContaining('jsx-runtime.js'), - ] }); } if (file.endsWith('one-import.spec.tsx')) { @@ -211,10 +222,6 @@ test('should extract component list', async ({ runInlineTest }, testInfo) => { components: [ expect.stringContaining('button_tsx_Button'), ], - deps: [ - expect.stringContaining('button.tsx'), - expect.stringContaining('jsx-runtime.js'), - ] }); } } @@ -436,3 +443,73 @@ test('list compilation cache should not clash with the run one', async ({ runInl expect(runResult.exitCode).toBe(0); expect(runResult.passed).toBe(1); }); + +test('should retain deps when test changes', async ({ runInlineTest }, testInfo) => { + test.slow(); + + await test.step('original test', async () => { + const result = await runInlineTest({ + 'playwright.config.ts': playwrightConfig, + 'playwright/index.html': ``, + 'playwright/index.ts': ``, + 'src/button.tsx': ` + export const Button = () => ; + `, + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button.tsx'; + test('pass', async ({ mount }) => { + const component = await mount(); + await expect(component).toHaveText('Button'); + }); + `, + }, { workers: 1 }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + const output = result.output; + expect(output).toContain('modules transformed'); + }); + + await test.step('modify test and run it again', async () => { + const result = await runInlineTest({ + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button.tsx'; + test('pass', async ({ mount }) => { + const component1 = await mount(); + await expect(component1).toHaveText('Button'); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + const output = result.output; + expect(output).not.toContain('modules transformed'); + }); + + const metainfo = JSON.parse(fs.readFileSync(testInfo.outputPath('playwright/.cache/metainfo.json'), 'utf-8')); + + expect(metainfo.components).toEqual([{ + fullName: expect.stringContaining('playwright_test_src_button_tsx_Button'), + importedName: 'Button', + importPath: expect.stringContaining('button.tsx'), + isModuleOrAlias: false, + deps: [ + expect.stringContaining('button.tsx'), + expect.stringContaining('jsx-runtime.js'), + ] + }]); + + expect(Object.entries(metainfo.tests)).toEqual([ + [ + expect.stringContaining('button.test.tsx'), + { + components: [ + expect.stringContaining('src_button_tsx_Button'), + ], + timestamp: expect.any(Number) + } + ] + ]); +}); diff --git a/tests/playwright-test/ui-mode-test-ct.spec.ts b/tests/playwright-test/ui-mode-test-ct.spec.ts new file mode 100644 index 0000000000000..13cfb49d56aa5 --- /dev/null +++ b/tests/playwright-test/ui-mode-test-ct.spec.ts @@ -0,0 +1,209 @@ +/** + * 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 { test, expect, retries, dumpTestTree } from './ui-mode-fixtures'; + +test.describe.configure({ mode: 'parallel', retries }); + +const basicTestTree = { + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/experimental-ct-react'; + export default defineConfig({}); + `, + 'playwright/index.html': ``, + 'playwright/index.ts': ``, + 'src/button.tsx': ` + export const Button = () => ; + `, + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button'; + + test('pass', async ({ mount }) => { + const component = await mount(); + await expect(component).toHaveText('Button', { timeout: 1 }); + }); + `, +}; + +test('should run component tests', async ({ runUITest }) => { + const { page } = await runUITest(basicTestTree); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass + `); + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass + `); +}); + +test('should run component tests after editing test', async ({ runUITest, writeFiles }) => { + const { page } = await runUITest(basicTestTree); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass + `); + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass + `); + + await writeFiles({ + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button'; + + test('fail', async ({ mount }) => { + const component = await mount(); + await expect(component).toHaveText('Button2', { timeout: 1 }); + }); + ` + }); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ fail + `); + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ❌ button.test.tsx + ❌ fail <= + `); +}); + +test('should run component tests after editing component', async ({ runUITest, writeFiles }) => { + const { page } = await runUITest(basicTestTree); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass + `); + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass + `); + + await writeFiles({ + 'src/button.tsx': ` + export const Button = () => ; + ` + }); + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ❌ button.test.tsx + ❌ pass <= + `); +}); + +test('should run component tests after editing test and component', async ({ runUITest, writeFiles }) => { + const { page } = await runUITest(basicTestTree); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass + `); + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass + `); + + await writeFiles({ + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button'; + + test('pass 2', async ({ mount }) => { + const component = await mount(); + await expect(component).toHaveText('Button2', { timeout: 1 }); + }); + `, + 'src/button.tsx': ` + export const Button = () => ; + ` + }); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass 2 + `); + + await page.getByTitle('Run all').click(); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass 2 + `); +}); + +test('should watch test', async ({ runUITest, writeFiles }) => { + const { page } = await runUITest(basicTestTree); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass + `); + + await page.getByTitle('Watch all').click(); + await page.getByTitle('Run all').click(); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass + `); + + await writeFiles({ + 'src/button.test.tsx': ` + import { test, expect } from '@playwright/experimental-ct-react'; + import { Button } from './button'; + + test('pass', async ({ mount }) => { + const component = await mount(); + await expect(component).toHaveText('Button2', { timeout: 1 }); + }); + ` + }); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ❌ button.test.tsx + ❌ pass <= + `); +}); + +test('should watch component', async ({ runUITest, writeFiles }) => { + const { page } = await runUITest(basicTestTree); + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ◯ button.test.tsx + ◯ pass + `); + + await page.getByTitle('Watch all').click(); + await page.getByTitle('Run all').click(); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ✅ button.test.tsx + ✅ pass + `); + + await writeFiles({ + 'src/button.tsx': ` + export const Button = () => ; + ` + }); + + await expect.poll(dumpTestTree(page)).toBe(` + ▼ ❌ button.test.tsx + ❌ pass <= + `); +}); From 77f4f4d5deb3249ad83c09755932c84736787878 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 24 Aug 2023 17:45:18 -0700 Subject: [PATCH 06/22] fix: linkify report link in check output (#26696) Fixes #26597 --- .github/workflows/create_test_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/create_test_report.yml b/.github/workflows/create_test_report.yml index 31db8616a4fa4..f0b4a00b93172 100644 --- a/.github/workflows/create_test_report.yml +++ b/.github/workflows/create_test_report.yml @@ -121,6 +121,7 @@ jobs: title: 'Test results for "${{ github.event.workflow_run.name }}"', summary: [ reportMd, + '', '---', `Full [HTML report](${reportUrl}). Merge [workflow run](${mergeWorkflowUrl}).` ].join('\n'), From 70dcaabdee7d700bb7a4a6f77fd980935bc9dec8 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 25 Aug 2023 17:40:26 +0200 Subject: [PATCH 07/22] fix: pass sdkLanguage to service server (#26702) --- .../playwright-core/src/remote/playwrightConnection.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/remote/playwrightConnection.ts b/packages/playwright-core/src/remote/playwrightConnection.ts index 447140da10eb1..c948ecb84bace 100644 --- a/packages/playwright-core/src/remote/playwrightConnection.ts +++ b/packages/playwright-core/src/remote/playwrightConnection.ts @@ -16,6 +16,7 @@ import type { WebSocket } from '../utilsBundle'; import type { DispatcherScope, Playwright } from '../server'; +import type * as channels from '@protocol/channels'; import { createPlaywright, DispatcherConnection, RootDispatcher, PlaywrightDispatcher } from '../server'; import { Browser } from '../server/browser'; import { serverSideCallMetadata } from '../server/instrumentation'; @@ -94,21 +95,21 @@ export class PlaywrightConnection { return; } - this._root = new RootDispatcher(this._dispatcherConnection, async scope => { + this._root = new RootDispatcher(this._dispatcherConnection, async (scope, options) => { await startProfiling(); if (clientType === 'reuse-browser') return await this._initReuseBrowsersMode(scope); if (clientType === 'pre-launched-browser-or-android') return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope); if (clientType === 'launch-browser') - return await this._initLaunchBrowserMode(scope); + return await this._initLaunchBrowserMode(scope, options); throw new Error('Unsupported client type: ' + clientType); }); } - private async _initLaunchBrowserMode(scope: RootDispatcher) { + private async _initLaunchBrowserMode(scope: RootDispatcher, options: channels.RootInitializeParams) { debugLogger.log('server', `[${this._id}] engaged launch mode for "${this._options.browserName}"`); - const playwright = createPlaywright({ sdkLanguage: 'javascript', isServer: true }); + const playwright = createPlaywright({ sdkLanguage: options.sdkLanguage, isServer: true }); const ownedSocksProxy = await this._createOwnedSocksProxy(playwright); const browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions); From 9ae77a71fd6843a8c6a9a559fd0c5043690ba76d Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Fri, 25 Aug 2023 19:06:49 +0200 Subject: [PATCH 08/22] feat: print message if maxFailures has reached (#26322) Fixes https://github.com/microsoft/playwright/issues/24239 --- .../playwright-test/src/reporters/base.ts | 10 +++++++ tests/playwright-test/reporter-base.spec.ts | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/playwright-test/src/reporters/base.ts b/packages/playwright-test/src/reporters/base.ts index 12d1d0fe33954..33e384f10c703 100644 --- a/packages/playwright-test/src/reporters/base.ts +++ b/packages/playwright-test/src/reporters/base.ts @@ -55,6 +55,7 @@ export class BaseReporter implements ReporterV2 { private _omitFailures: boolean; private readonly _ttyWidthForTest: number; private _fatalErrors: TestError[] = []; + private _failureCount: number = 0; constructor(options: { omitFailures?: boolean } = {}) { this._omitFailures = options.omitFailures || false; @@ -94,6 +95,8 @@ export class BaseReporter implements ReporterV2 { } onTestEnd(test: TestCase, result: TestResult) { + if (result.status !== 'skipped' && result.status !== test.expectedStatus) + ++this._failureCount; // Ignore any tests that are run in parallel. for (let suite: Suite | undefined = test.parent; suite; suite = suite.parent) { if ((suite as SuitePrivate)._parallelMode === 'parallel') @@ -232,6 +235,7 @@ export class BaseReporter implements ReporterV2 { if (full && summary.failuresToPrint.length && !this._omitFailures) this._printFailures(summary.failuresToPrint); this._printSlowTests(); + this._printMaxFailuresReached(); this._printSummary(summaryMessage); } @@ -253,6 +257,12 @@ export class BaseReporter implements ReporterV2 { console.log(colors.yellow(' Consider splitting slow test files to speed up parallel execution')); } + private _printMaxFailuresReached() { + if (this.config.maxFailures && this._failureCount < this.config.maxFailures) + return; + console.log(colors.yellow(`Testing stopped early after ${this.config.maxFailures} maximum allowed failures.`)); + } + private _printSummary(summary: string) { if (summary.trim()) console.log(summary); diff --git a/tests/playwright-test/reporter-base.spec.ts b/tests/playwright-test/reporter-base.spec.ts index 494180ec539dd..7b0c5a40a4451 100644 --- a/tests/playwright-test/reporter-base.spec.ts +++ b/tests/playwright-test/reporter-base.spec.ts @@ -182,6 +182,33 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(result.output).not.toContain(`Slow test file: [qux] › dir${path.sep}b.test.js (`); }); + test('should print if maxFailures is reached', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + module.exports = { + maxFailures: 1, + }; + `, + 'dir/a.test.js': ` + import { test, expect } from '@playwright/test'; + test('failing1', async ({}) => { + expect(1).toBe(2); + }); + test('failing2', async ({}) => { + expect(1).toBe(2); + }); + test('failing3', async ({}) => { + expect(1).toBe(2); + }); + `, + }); + expect(result.exitCode).toBe(1); + expect(result.failed).toBe(1); + expect(result.passed).toBe(0); + expect(result.skipped).toBe(2); + expect(result.output).toContain('Testing stopped early after 1 maximum allowed failures.'); + }); + test('should not print slow parallel tests', async ({ runInlineTest }) => { const result = await runInlineTest({ 'playwright.config.ts': ` From ce446437fb10d2f8e0bf39dac2a7e62cea1530c8 Mon Sep 17 00:00:00 2001 From: Playwright Service <89237858+playwrightmachine@users.noreply.github.com> Date: Fri, 25 Aug 2023 10:31:19 -0700 Subject: [PATCH 09/22] feat(chromium): roll to r1077 (#26709) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- README.md | 4 +- packages/playwright-core/browsers.json | 8 +- .../src/server/chromium/protocol.d.ts | 167 +++++++++++++++--- .../src/server/deviceDescriptorsSource.json | 92 +++++----- packages/playwright-core/types/protocol.d.ts | 167 +++++++++++++++--- 5 files changed, 334 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index ab4cb54cea324..1e23c7d71a66e 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-116.0.5845.82-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-115.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.0-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-117.0.5938.22-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-115.0-blue.svg?logo=firefoxbrowser)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-17.0-blue.svg?logo=safari)](https://webkit.org/) ## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright) @@ -8,7 +8,7 @@ Playwright is a framework for Web Testing and Automation. It allows testing [Chr | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 116.0.5845.82 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Chromium 117.0.5938.22 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 17.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Firefox 115.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 1e0219c80d193..fc8686706865b 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -3,15 +3,15 @@ "browsers": [ { "name": "chromium", - "revision": "1076", + "revision": "1077", "installByDefault": true, - "browserVersion": "116.0.5845.82" + "browserVersion": "117.0.5938.22" }, { "name": "chromium-with-symbols", - "revision": "1076", + "revision": "1077", "installByDefault": false, - "browserVersion": "116.0.5845.82" + "browserVersion": "117.0.5938.22" }, { "name": "chromium-tip-of-tree", diff --git a/packages/playwright-core/src/server/chromium/protocol.d.ts b/packages/playwright-core/src/server/chromium/protocol.d.ts index c10790105e688..6844769c84548 100644 --- a/packages/playwright-core/src/server/chromium/protocol.d.ts +++ b/packages/playwright-core/src/server/chromium/protocol.d.ts @@ -43,7 +43,7 @@ export module Protocol { */ superseded?: boolean; /** - * The native markup source for this value, e.g. a