Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions packages/browser-test-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@
"scripts": {
"ci:playwright": "cd ../../submodules/playwright && npm ci --ignore-scripts",
"generate-injected:core": "cd ../../submodules/playwright && node ./utils/generate_injected.js",
"ci:core:bundles:utils": "cd ../../submodules/playwright/packages/playwright-core/bundles/utils && npm ci",
"ci:core:bundles:zip": "cd ../../submodules/playwright/packages/playwright-core/bundles/zip && npm ci",
"ci:core:bundles:mcp": "cd ../../submodules/playwright/packages/playwright-core/bundles/mcp && npm ci",
"ci:test:bundles:expect": "cd ../../submodules/playwright/packages/playwright/bundles/expect && npm ci",
"ci:bundles:pngjs": "cd bundles/pngjs && npm ci",
"ci:bundles": "npm run ci:core:bundles:utils && npm run ci:core:bundles:zip && npm run ci:core:bundles:mcp && npm run ci:test:bundles:expect && npm run ci:bundles:pngjs",
"ci:bundles": "npm run ci:bundles:pngjs",
"build:bundles": "node utils/build_bundles.js",
"build": "node ../../scripts/with-patch.mjs playwright npm run build:patched",
"build:patched": "npm run ci:playwright && npm run generate-injected:core && npm run ci:bundles && npm run build:bundles && vite build"
Expand Down
27 changes: 17 additions & 10 deletions packages/browser-test-runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import fs from 'fs';

import {asLocatorDescription} from '@isomorphic/locatorGenerators';
import {ManualPromise} from '@isomorphic/manualPromise';
import {
asLocatorDescription,
currentZone,
ManualPromise,
getActionGroup,
renderTitleForCall,
setTimeOrigin,
timeOrigin,
} from 'playwright-core/lib/utils';
} from '@isomorphic/protocolFormatter';
import {setTimeOrigin, timeOrigin} from '@isomorphic/time';
import {currentZone} from '@utils/zones';
import {loadConfig} from 'playwright/lib/common/configLoader';
import {
currentTestInfo,
setCurrentlyLoadingFileSuite,
} from 'playwright/lib/common/globals';
} from 'playwright/lib/globals';
import {bindFileSuiteToProject} from 'playwright/lib/common/suiteUtils';
import {Suite, TestCase} from 'playwright/lib/common/test';
import {rootTestType} from 'playwright/lib/common/testType';
Expand Down Expand Up @@ -146,7 +146,8 @@ class TestWorker extends WorkerMain {
configCLIOverrides: {timeout: 5000, ...options},
},
artifactsDir: '/tmp/tests',
recoverFromStepErrors: false,
pauseOnError: false,
pauseAtEnd: false,
});
}

Expand Down Expand Up @@ -219,7 +220,11 @@ export class TestRunner {
const {retry} = this.testContext;
const [result] = await Promise.all([
worker.result(),
worker.runTestGroup({file, entries: [{testId, retry}]}),
// plan annotations are applied by the test runner, which does not run here
worker.runTestGroup({
file,
entries: [{testId, retry, planAnnotations: []}],
}),
]);
if (
result.status === 'failed' &&
Expand Down Expand Up @@ -254,7 +259,8 @@ const expectApiListener: ClientInstrumentationListener = {
const zone = currentZone().data<TestStepInternal>('stepZone');
if (zone?.category === 'expect') {
if (zone.apiName) data.apiName = zone.apiName;
if (zone.title) data.title = zone.title;
if (zone.shortTitle || zone.title)
data.title = zone.shortTitle ?? zone.title;
data.stepId = zone.stepId;
return;
}
Expand All @@ -271,6 +277,7 @@ const expectApiListener: ClientInstrumentationListener = {
),
apiName: data.apiName,
params: channel.params,
group: getActionGroup({type: channel.type, method: channel.method}),
},
tracingGroupSteps[tracingGroupSteps.length - 1],
);
Expand Down
6 changes: 6 additions & 0 deletions packages/browser-test-runtime/src/mocks/coreBundle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// `playwright-core/lib/coreBundle` re-exports all of playwright-core (CLI, MCP tools, servers).
// Workers code only needs these exports, so the full barrel is not bundled.
export {
getPlaywrightVersion,
getUserAgent,
} from 'playwright-core/lib/server/userAgent';
7 changes: 0 additions & 7 deletions packages/browser-test-runtime/src/mocks/esmLoaderHost.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/browser-test-runtime/src/mocks/transform.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {currentlyLoadingFileSuite} from 'playwright/lib/common/globals';
import {currentlyLoadingFileSuite} from 'playwright/lib/globals';

import {configLocation, playwrightTestConfig} from '../state';

Expand Down
2 changes: 2 additions & 0 deletions packages/browser-test-runtime/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const playwrightTestConfig = {
testDir: '/bundle',
},
],
// same as submodules/playwright/tests/library/playwright.config.ts
expect: {timeout: 10000},
};

export const configLocation = {
Expand Down
109 changes: 71 additions & 38 deletions packages/browser-test-runtime/utils/build_bundles.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,38 @@ import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const bundles = {
// pngjs needs to bundle browserify-zlib, it throws an error when trying to use workers runtime zlib:
// Error: Class constructor Inflate cannot be invoked without 'new'
// It needs to build before other bundles, because they may depend on it
'pngjs': '../bundles/pngjs',
'utilsBundleImpl': '../../../submodules/playwright/packages/playwright-core/bundles/utils',
'zipBundleImpl': '../../../submodules/playwright/packages/playwright-core/bundles/zip',
'mcpBundleImpl': '../../../submodules/playwright/packages/playwright-core/bundles/mcp',
'expectBundleImpl': '../../../submodules/playwright/packages/playwright/bundles/expect',
};
const basedir = path.dirname(fileURLToPath(import.meta.url));
const playwrightDir = path.join(basedir, '..', '..', '..', 'submodules', 'playwright');
const jestDir = path.join(basedir, '..', '..', '..', 'submodules', 'jest');
const outDir = path.join(basedir, '../src/bundles');

const bundles = [
{
// pngjs needs to bundle browserify-zlib, it throws an error when trying to use workers runtime zlib:
// Error: Class constructor Inflate cannot be invoked without 'new'
// It needs to build before other bundles, because they may depend on it
name: 'pngjs',
root: path.join(basedir, '../bundles/pngjs'),
entry: { pngjs: path.join(basedir, '../bundles/pngjs/src/pngjs.ts') },
},
{
// Upstream vendors all npm dependencies of playwright-core through this single barrel.
name: 'utilsBundleImpl',
root: playwrightDir,
entry: { utilsBundleImpl: path.join(playwrightDir, 'packages/playwright-core/src/utilsBundle.ts') },
},
{
// Jest helpers used by playwright's inlined expect library (matchers/expectLibrary.ts).
// Each entry keeps the original package exports, so imports can be aliased one-to-one.
name: 'jest',
root: playwrightDir,
entry: {
'jest-matcher-utils': path.join(jestDir, 'packages/jest-matcher-utils/src/index.ts'),
'jest-message-util': path.join(jestDir, 'packages/jest-message-util/src/index.ts'),
'jest-expect-utils': path.join(jestDir, 'packages/expect-utils/src/index.ts'),
},
},
];

const external = [
'assert',
Expand All @@ -37,6 +59,7 @@ const external = [
'tls',
'url',
'util',
'readline',
// Node.js prefixed modules
'node:crypto',
'node:url',
Expand All @@ -56,36 +79,39 @@ const external = [
'node:string_decoder',
'node:tls',
'node:os',
'node:readline',
];

const basedir = path.dirname(fileURLToPath(import.meta.url));
function listNodeModules(root) {
const nodeModulesDir = path.join(root, 'node_modules');
return fs.readdirSync(nodeModulesDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.flatMap(dirent => dirent.name.startsWith('@')
// expand scoped packages (the ones that start with @)
? fs.readdirSync(path.join(nodeModulesDir, dirent.name), { withFileTypes: true })
.filter(subdirent => subdirent.isDirectory())
.map(subdirent => `${dirent.name}/${subdirent.name}`)
: [dirent.name]);
}

(async () => {
for (const [name, bundleDir] of Object.entries(bundles)) {
const root = path.join(basedir, bundleDir);
const nodeModulesDir = path.join(root, 'node_modules');
const nodeModulesLibs = fs.readdirSync(nodeModulesDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.flatMap(dirent => dirent.name.startsWith('@')
// expand scoped packages (the ones that start with @)
? fs.readdirSync(path.join(nodeModulesDir, dirent.name), { withFileTypes: true })
.filter(subdirent => subdirent.isDirectory())
.map(subdirent => `${dirent.name}/${subdirent.name}`)
: [dirent.name]);
const jestDir = path.join(basedir, '..', '..', '..', 'submodules', 'jest');
// we'll use jest packages source code instead of npm packages
const jestPackages = Object.fromEntries(fs.readdirSync(path.join(jestDir, 'packages'), { withFileTypes: true })
.filter(dirent => dirent.isDirectory() &&
fs.existsSync(path.join(jestDir, 'packages', dirent.name, 'package.json')) &&
fs.existsSync(path.join(jestDir, 'packages', dirent.name, 'src', 'index.ts')))
.map(dirent => [
JSON.parse(fs.readFileSync(path.join(jestDir, 'packages', dirent.name, 'package.json'), 'utf-8')).name,
path.join(jestDir, 'packages', dirent.name, 'src', 'index.ts')
]));

// we'll use jest packages source code instead of npm packages
const jestPackages = Object.fromEntries(fs.readdirSync(path.join(jestDir, 'packages'), { withFileTypes: true })
.filter(dirent => dirent.isDirectory() &&
fs.existsSync(path.join(jestDir, 'packages', dirent.name, 'package.json')) &&
fs.existsSync(path.join(jestDir, 'packages', dirent.name, 'src', 'index.ts')))
.map(dirent => [
JSON.parse(fs.readFileSync(path.join(jestDir, 'packages', dirent.name, 'package.json'), 'utf-8')).name,
path.join(jestDir, 'packages', dirent.name, 'src', 'index.ts')
]));
(async () => {
for (const { name, root, entry } of bundles) {
// jest sources live outside the root, so their npm dependencies must resolve from the root node_modules
const nodeModulesLibs = name === 'jest' ? listNodeModules(root).filter(lib => !(lib in jestPackages)) : [];

await build({
root,
configFile: false,
// Jest is source input here, not a separately installed project.
esbuild: { tsconfigRaw: {} },
resolve: {
Expand All @@ -98,15 +124,19 @@ const basedir = path.dirname(fileURLToPath(import.meta.url));
'node:process': 'process',
'node:string_decoder': 'string_decoder',

// jest npm package is commonjs,
// jest npm package is commonjs,
...jestPackages,

'commander': path.join(basedir, '../src/mocks/commander'),
'socks-proxy-agent': path.join(basedir, '../src/mocks/socksProxyAgent'),
'open': path.join(basedir, '../src/mocks/open'),
// optional native macOS dependency of chokidar
'fsevents': path.join(basedir, '../src/mocks/empty'),
// same replacement upstream uses when bundling utilsBundle (utils/build/build.js)
'raw-body': path.join(playwrightDir, 'utils/build/raw-body.ts'),

...(name === 'pngjs' ? { 'zlib': 'browserify-zlib' } :
{ 'pngjs': path.join(basedir, `../src/bundles/pngjs.js` ) }),
{ 'pngjs': path.join(outDir, 'pngjs.js') }),
},
},
build: {
Expand All @@ -116,23 +146,26 @@ const basedir = path.dirname(fileURLToPath(import.meta.url));
target: 'esnext',
lib: {
name,
entry: path.join(root, `./src/${name}.ts`),
entry,
formats: ['es'],
},
rollupOptions: {
external: [
/^node:/,
...(name === 'pngjs' ? external
: [...external, 'zlib', 'pngjs'])
],
output: {
dir: path.join(basedir, '../src/bundles'),
entryFileNames: `${name}.js`,
dir: outDir,
entryFileNames: '[name].js',
chunkFileNames: `${name}-[name].js`,
},
},
commonjsOptions: {
transformMixedEsModules: true,
extensions: ['.ts', '.js'],
include: [
...Object.values(entry),
path.join(root, './src/**/*'),
path.join(root, './node_modules/**/*'),
],
Expand Down
Loading
Loading