Skip to content

Commit

Permalink
fix(runner): minimize wrightplay pollution (#21)
Browse files Browse the repository at this point in the history
* Fix the source-mapped stack traces for entry points that is not a direct child of cwd.
* Move wrightplay entry point to `__wrightplay__/stdin`.
* Remove the injected script element once it starts to load.
* Discard log errors that is caused by auto rerun navigations.
* Sort test imports to ignore irrelevant file watch events.
* Suggest the `fullTrace` option for mocha use cases.
  • Loading branch information
PaperStrike authored Oct 27, 2023
1 parent e0237ce commit 546517f
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 35 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,10 @@ import 'mocha';
import { onInit, done } from 'wrightplay';

mocha.setup({
ui: 'bdd',
reporter: 'spec',
color: true,
fullTrace: true,
reporter: 'spec',
ui: 'bdd',
});

onInit(() => {
Expand Down
23 changes: 13 additions & 10 deletions src/client/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,38 @@ export const inject = (uuid: string) => (
new Promise<number>((resolve) => {
const script = document.createElement('script');

// Avoid test interfering
// eslint-disable-next-line no-console
const consoleError = console.error;

// Detect inject error
script.addEventListener('error', () => {
// eslint-disable-next-line no-console
console.error('Failed to inject test script');
consoleError('Failed to inject test script');
resolve(1);
}, { once: true });

// Detect init error
const onUncaughtError = () => {
// eslint-disable-next-line no-console
console.error('Uncaught error detected while initializing the tests');
const initErrorListenerAbortController = new AbortController();
window.addEventListener('error', () => {
consoleError('Uncaught error detected while initializing the tests');
resolve(1);
};
window.addEventListener('error', onUncaughtError, { once: true });
}, { once: true, signal: initErrorListenerAbortController.signal });

// Detect init end
window.addEventListener(`__wrightplay_${uuid}_init__`, () => {
window.removeEventListener('error', onUncaughtError);
initErrorListenerAbortController.abort();
}, { once: true });

// Detect test done
window.addEventListener(`__wrightplay_${uuid}_done__`, ({ exitCode }) => {
window.removeEventListener('error', onUncaughtError);
initErrorListenerAbortController.abort();
resolve(exitCode);
}, { once: true });

// Inject
script.src = '/stdin.js';
script.src = '/__wrightplay__/stdin.js';
script.type = 'module';
document.head.append(script);
document.head.removeChild(script);
})
);
19 changes: 15 additions & 4 deletions src/server/BrowserLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,26 @@ export default class BrowserLogger {
originalColumn,
} = sourceMap.findEntry(+line - 1, +column - 1);

// The type of `findEntry` is loose. It may return {}.
return originalSource !== undefined
? `${pathToFileURL(path.resolve(cwd, originalSource)).href}:${originalLine + 1}:${originalColumn + 1}`
: original;
// The return type of `findEntry` is inaccurate. It may return {}.
if (originalSource === undefined) {
return original;
}

const baseDir = path.join(cwd, path.dirname(pathname));
const originalSourcePath = path.resolve(baseDir, originalSource);
return `${pathToFileURL(originalSourcePath).href}:${originalLine + 1}:${originalColumn + 1}`;
});
}

lastPrint: Promise<void> = Promise.resolve();

/**
* Discard the last print error.
*/
discardLastPrintError() {
this.lastPrint = this.lastPrint.catch(() => {});
}

/**
* Print messages to console with specified log level and color.
*/
Expand Down
58 changes: 43 additions & 15 deletions src/server/Runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export default class Runner implements Disposable {
private updateBuiltFiles(files: esbuild.OutputFile[]) {
const { cwd, fileContents, sourceMapPayloads } = this;
return files.reduce((changed, { path: absPath, hash, text }) => {
const pathname = `/${path.relative(cwd, absPath)}`;
const pathname = `/${path.relative(cwd, absPath).replace(/\\/g, '/')}`;

// Skip unchanged files.
const same = fileContents.get(pathname)?.hash === hash;
Expand Down Expand Up @@ -241,7 +241,7 @@ export default class Runner implements Disposable {
// The stdin API doesn't support onLoad callbacks,
// so we use the entry point workaround.
// https://github.com/evanw/esbuild/issues/720
stdin: '<stdin>',
'__wrightplay__/stdin': '<stdin>',
},
metafile: watch,
bundle: true,
Expand All @@ -254,10 +254,15 @@ export default class Runner implements Disposable {
{
name: 'import files loader',
setup: (pluginBuild) => {
pluginBuild.onResolve({ filter: /^<stdin>$/ }, () => ({ path: 'stdin', namespace: 'stdin' }));
pluginBuild.onLoad({ filter: /^/, namespace: 'stdin' }, async () => {
pluginBuild.onResolve({ filter: /^<stdin>$/ }, () => ({ path: 'stdin', namespace: 'wrightplay' }));
pluginBuild.onLoad({ filter: /^/, namespace: 'wrightplay' }, async () => {
// Sort to make the output stable
const importFiles = await testFinder.getFiles();
importFiles.sort();

// Prepend the setup file if any
if (setupFile) importFiles.unshift(setupFile.replace(/\\/g, '\\\\'));

if (importFiles.length === 0) {
if (watch) {
// eslint-disable-next-line no-console
Expand All @@ -266,6 +271,7 @@ export default class Runner implements Disposable {
throw new Error('No test file found');
}
}

const importStatements = importFiles.map((file) => `import '${file}'`).join('\n');
return {
contents: `${importStatements}\n(${clientRunner.init.toString()})('${this.uuid}')`,
Expand Down Expand Up @@ -349,8 +355,7 @@ export default class Runner implements Disposable {
}

const esbuildListener: http.RequestListener = (request, response) => {
const { url } = request as typeof request & { url: string };
const pathname = url.split(/[?#]/, 1)[0];
const { pathname } = new URL(request.url!, `http://${request.headers.host}`);

const handleRequest = () => {
const builtContent = fileContents.get(pathname);
Expand Down Expand Up @@ -460,20 +465,43 @@ export default class Runner implements Disposable {

const wsServer = new WSServer(this.uuid, fileServer, page);
const run = async () => {
await wsServer.reset();
return page.evaluate(clientRunner.inject, this.uuid)
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
return 1;
});
// Listen to the file change event during the test run to
// ignore the evaluate error caused by automatic test reruns.
let fileChanged = false;
const fileChangeListener = () => { fileChanged = true; };
fileServer.once('wrightplay:changed', fileChangeListener);

try {
await wsServer.reset();
return await page.evaluate(clientRunner.inject, this.uuid);
} catch (error) {
// Skip the error print if the file has changed.
// eslint-disable-next-line no-console
if (!fileChanged) console.error(error);
return 1;
} finally {
// Remove the listener to avoid potential memory leak.
fileServer.off('wrightplay:changed', fileChangeListener);
}
};

await page.goto('/');
await page.goto('/', { waitUntil: 'domcontentloaded' });

// Rerun the tests on file changes.
fileServer.on('wrightplay:changed', () => {
page.reload().catch(() => {
(async () => {
// Discard the print error on navigation.
page.off('console', bLog.forwardConsole);
page.off('pageerror', bLog.forwardError);
bLog.discardLastPrintError();

// Reload the page to rerun the tests.
await page.reload({ waitUntil: 'commit' });

// Restore the print forwarding.
page.on('console', bLog.forwardConsole);
page.on('pageerror', bLog.forwardError);
})().catch(() => {
// eslint-disable-next-line no-console
console.error('Failed to rerun the tests after file changes');
});
Expand Down
5 changes: 3 additions & 2 deletions test/default.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ const {
const { onInit, done } = await import('../src/client/api.js');

mocha.setup({
ui: 'bdd',
reporter: 'spec',
color: true,
fullTrace: true,
reporter: 'spec',
ui: 'bdd',
});

onInit(() => {
Expand Down
5 changes: 3 additions & 2 deletions test/third-party/mocha/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import 'mocha';
import { onInit, done } from 'wrightplay';

mocha.setup({
ui: 'bdd',
reporter: 'spec',
color: true,
fullTrace: true,
reporter: 'spec',
ui: 'bdd',
});

onInit(() => {
Expand Down

0 comments on commit 546517f

Please sign in to comment.