mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(test runner): allow stopping testrun with escape (#32584)
Closes https://github.com/microsoft/playwright/issues/32579
This commit is contained in:
parent
5121b19ac6
commit
892d67ffef
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import readline from 'readline';
|
import readline from 'readline';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createGuid, getPackageManagerExecCommand, ManualPromise } from 'playwright-core/lib/utils';
|
import { createGuid, eventsHelper, getPackageManagerExecCommand, ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import type { ConfigLocation } from '../common/config';
|
import type { ConfigLocation } from '../common/config';
|
||||||
import type { FullResult } from '../../types/testReporter';
|
import type { FullResult } from '../../types/testReporter';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
@ -266,12 +266,47 @@ export async function runWatchModeLoop(configLocation: ConfigLocation, initialOp
|
|||||||
return result === 'passed' ? teardown.status : result;
|
return result === 'passed' ? teardown.status : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readKeyPress<T extends string>(handler: (text: string, key: any) => T | undefined): { cancel(): void; result: Promise<T> } {
|
||||||
|
const promise = new ManualPromise<T>();
|
||||||
|
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 });
|
||||||
|
readline.emitKeypressEvents(process.stdin, rl);
|
||||||
|
if (process.stdin.isTTY)
|
||||||
|
process.stdin.setRawMode(true);
|
||||||
|
|
||||||
|
const listener = eventsHelper.addEventListener(process.stdin, 'keypress', (text: string, key: any) => {
|
||||||
|
const result = handler(text, key);
|
||||||
|
if (result)
|
||||||
|
promise.resolve(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
eventsHelper.removeEventListeners([listener]);
|
||||||
|
rl.close();
|
||||||
|
if (process.stdin.isTTY)
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
void promise.finally(cancel);
|
||||||
|
|
||||||
|
return { result: promise, cancel };
|
||||||
|
}
|
||||||
|
|
||||||
|
const isInterrupt = (text: string, key: any) => text === '\x03' || text === '\x1B' || (key && key.name === 'escape') || (key && key.ctrl && key.name === 'c');
|
||||||
|
|
||||||
async function runTests(watchOptions: WatchModeOptions, testServerConnection: TestServerConnection, options?: {
|
async function runTests(watchOptions: WatchModeOptions, testServerConnection: TestServerConnection, options?: {
|
||||||
title?: string,
|
title?: string,
|
||||||
testIds?: string[],
|
testIds?: string[],
|
||||||
}) {
|
}) {
|
||||||
printConfiguration(watchOptions, options?.title);
|
printConfiguration(watchOptions, options?.title);
|
||||||
|
|
||||||
|
const waitForDone = readKeyPress((text: string, key: any) => {
|
||||||
|
if (isInterrupt(text, key)) {
|
||||||
|
testServerConnection.stopTestsNoReply({});
|
||||||
|
return 'done';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await testServerConnection.runTests({
|
await testServerConnection.runTests({
|
||||||
grep: watchOptions.grep,
|
grep: watchOptions.grep,
|
||||||
testIds: options?.testIds,
|
testIds: options?.testIds,
|
||||||
@ -281,30 +316,21 @@ async function runTests(watchOptions: WatchModeOptions, testServerConnection: Te
|
|||||||
reuseContext: connectWsEndpoint ? true : undefined,
|
reuseContext: connectWsEndpoint ? true : undefined,
|
||||||
workers: connectWsEndpoint ? 1 : undefined,
|
workers: connectWsEndpoint ? 1 : undefined,
|
||||||
headed: connectWsEndpoint ? true : undefined,
|
headed: connectWsEndpoint ? true : undefined,
|
||||||
});
|
}).finally(() => waitForDone.cancel());
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCommand(): { result: Promise<Command>, cancel: () => void } {
|
function readCommand() {
|
||||||
const result = new ManualPromise<Command>();
|
return readKeyPress<Command>((text: string, key: any) => {
|
||||||
const rl = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 });
|
if (isInterrupt(text, key))
|
||||||
readline.emitKeypressEvents(process.stdin, rl);
|
return 'interrupted';
|
||||||
if (process.stdin.isTTY)
|
|
||||||
process.stdin.setRawMode(true);
|
|
||||||
|
|
||||||
const handler = (text: string, key: any) => {
|
|
||||||
if (text === '\x03' || text === '\x1B' || (key && key.name === 'escape') || (key && key.ctrl && key.name === 'c')) {
|
|
||||||
result.resolve('interrupted');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (process.platform !== 'win32' && key && key.ctrl && key.name === 'z') {
|
if (process.platform !== 'win32' && key && key.ctrl && key.name === 'z') {
|
||||||
process.kill(process.ppid, 'SIGTSTP');
|
process.kill(process.ppid, 'SIGTSTP');
|
||||||
process.kill(process.pid, 'SIGTSTP');
|
process.kill(process.pid, 'SIGTSTP');
|
||||||
}
|
}
|
||||||
const name = key?.name;
|
const name = key?.name;
|
||||||
if (name === 'q') {
|
if (name === 'q')
|
||||||
result.resolve('exit');
|
return 'exit';
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (name === 'h') {
|
if (name === 'h') {
|
||||||
process.stdout.write(`${separator()}
|
process.stdout.write(`${separator()}
|
||||||
Run tests
|
Run tests
|
||||||
@ -324,26 +350,16 @@ Change settings
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'return': result.resolve('run'); break;
|
case 'return': return 'run';
|
||||||
case 'r': result.resolve('repeat'); break;
|
case 'r': return 'repeat';
|
||||||
case 'c': result.resolve('project'); break;
|
case 'c': return 'project';
|
||||||
case 'p': result.resolve('file'); break;
|
case 'p': return 'file';
|
||||||
case 't': result.resolve('grep'); break;
|
case 't': return 'grep';
|
||||||
case 'f': result.resolve('failed'); break;
|
case 'f': return 'failed';
|
||||||
case 's': result.resolve('toggle-show-browser'); break;
|
case 's': return 'toggle-show-browser';
|
||||||
case 'b': result.resolve('toggle-buffer-mode'); break;
|
case 'b': return 'toggle-buffer-mode';
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
process.stdin.on('keypress', handler);
|
|
||||||
const cancel = () => {
|
|
||||||
process.stdin.off('keypress', handler);
|
|
||||||
rl.close();
|
|
||||||
if (process.stdin.isTTY)
|
|
||||||
process.stdin.setRawMode(false);
|
|
||||||
};
|
|
||||||
void result.finally(cancel);
|
|
||||||
return { result, cancel };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let showBrowserServer: PlaywrightServer | undefined;
|
let showBrowserServer: PlaywrightServer | undefined;
|
||||||
|
|||||||
@ -421,6 +421,7 @@ test('should re-run failed tests on F > R', async ({ runWatchTest }) => {
|
|||||||
await testProcess.waitForOutput('npx playwright test (running failed tests) #2');
|
await testProcess.waitForOutput('npx playwright test (running failed tests) #2');
|
||||||
await testProcess.waitForOutput('c.test.ts:3:11 › fails');
|
await testProcess.waitForOutput('c.test.ts:3:11 › fails');
|
||||||
expect(testProcess.output).not.toContain('a.test.ts:3:11');
|
expect(testProcess.output).not.toContain('a.test.ts:3:11');
|
||||||
|
await testProcess.waitForOutput('Waiting for file changes.');
|
||||||
testProcess.clearOutput();
|
testProcess.clearOutput();
|
||||||
testProcess.write('r');
|
testProcess.write('r');
|
||||||
await testProcess.waitForOutput('npx playwright test (re-running tests) #3');
|
await testProcess.waitForOutput('npx playwright test (re-running tests) #3');
|
||||||
@ -836,6 +837,25 @@ test('should run global teardown before exiting', async ({ runWatchTest }) => {
|
|||||||
await testProcess.waitForOutput('running teardown');
|
await testProcess.waitForOutput('running teardown');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should stop testrun on pressing escape', async ({ runWatchTest }) => {
|
||||||
|
const testProcess = await runWatchTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('stalls', async () => {
|
||||||
|
console.log('test started')
|
||||||
|
await new Promise(() => {});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
await testProcess.waitForOutput('Waiting for file changes.');
|
||||||
|
testProcess.clearOutput();
|
||||||
|
testProcess.write('\r\n');
|
||||||
|
|
||||||
|
await testProcess.waitForOutput('test started');
|
||||||
|
testProcess.write('\x1B');
|
||||||
|
await testProcess.waitForOutput('1 interrupted');
|
||||||
|
});
|
||||||
|
|
||||||
test('buffer mode', async ({ runWatchTest, writeFiles }) => {
|
test('buffer mode', async ({ runWatchTest, writeFiles }) => {
|
||||||
const testProcess = await runWatchTest({
|
const testProcess = await runWatchTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user