cherry-pick(#21787): chore: allow watching all tests
This commit is contained in:
parent
ec76a817ed
commit
53c40e24d2
|
|
@ -18,7 +18,7 @@ import { showTraceViewer } from 'playwright-core/lib/server';
|
||||||
import type { Page } from 'playwright-core/lib/server/page';
|
import type { Page } from 'playwright-core/lib/server/page';
|
||||||
import { isUnderTest, ManualPromise } from 'playwright-core/lib/utils';
|
import { isUnderTest, ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import type { FullResult } from '../../reporter';
|
import type { FullResult } from '../../reporter';
|
||||||
import { clearCompilationCache, dependenciesForTestFile } from '../common/compilationCache';
|
import { clearCompilationCache, collectAffectedTestFiles, dependenciesForTestFile } from '../common/compilationCache';
|
||||||
import type { FullConfigInternal } from '../common/types';
|
import type { FullConfigInternal } from '../common/types';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
import { Multiplexer } from '../reporters/multiplexer';
|
||||||
import { TeleReporterEmitter } from '../reporters/teleEmitter';
|
import { TeleReporterEmitter } from '../reporters/teleEmitter';
|
||||||
|
|
@ -34,7 +34,7 @@ class UIMode {
|
||||||
private _page!: Page;
|
private _page!: Page;
|
||||||
private _testRun: { run: Promise<FullResult['status']>, stop: ManualPromise<void> } | undefined;
|
private _testRun: { run: Promise<FullResult['status']>, stop: ManualPromise<void> } | undefined;
|
||||||
globalCleanup: (() => Promise<FullResult['status']>) | undefined;
|
globalCleanup: (() => Promise<FullResult['status']>) | undefined;
|
||||||
private _testWatcher: FSWatcher | undefined;
|
private _testWatcher: { watcher: FSWatcher, watchedFiles: string[], collector: Set<string>, timer?: NodeJS.Timeout } | undefined;
|
||||||
private _originalStderr: (buffer: string | Uint8Array) => void;
|
private _originalStderr: (buffer: string | Uint8Array) => void;
|
||||||
|
|
||||||
constructor(config: FullConfigInternal) {
|
constructor(config: FullConfigInternal) {
|
||||||
|
|
@ -187,22 +187,40 @@ class UIMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _watchFiles(fileNames: string[]) {
|
private async _watchFiles(fileNames: string[]) {
|
||||||
if (this._testWatcher)
|
|
||||||
await this._testWatcher.close();
|
|
||||||
if (!fileNames.length)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const files = new Set<string>();
|
const files = new Set<string>();
|
||||||
for (const fileName of fileNames) {
|
for (const fileName of fileNames) {
|
||||||
files.add(fileName);
|
files.add(fileName);
|
||||||
dependenciesForTestFile(fileName).forEach(file => files.add(file));
|
dependenciesForTestFile(fileName).forEach(file => files.add(file));
|
||||||
}
|
}
|
||||||
|
const watchedFiles = [...files].sort();
|
||||||
|
if (this._testWatcher && JSON.stringify(this._testWatcher.watchedFiles.toString()) === JSON.stringify(watchedFiles))
|
||||||
|
return;
|
||||||
|
|
||||||
this._testWatcher = chokidar.watch([...files], { ignoreInitial: true }).on('all', async (event, file) => {
|
if (this._testWatcher) {
|
||||||
|
if (this._testWatcher.collector.size)
|
||||||
|
this._dispatchEvent({ method: 'filesChanged', params: { fileNames: [...this._testWatcher.collector] } });
|
||||||
|
clearTimeout(this._testWatcher.timer);
|
||||||
|
this._testWatcher.watcher.close().then(() => {});
|
||||||
|
this._testWatcher = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!watchedFiles.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const collector = new Set<string>();
|
||||||
|
const watcher = chokidar.watch(watchedFiles, { ignoreInitial: true }).on('all', async (event, file) => {
|
||||||
if (event !== 'add' && event !== 'change')
|
if (event !== 'add' && event !== 'change')
|
||||||
return;
|
return;
|
||||||
this._dispatchEvent({ method: 'fileChanged', params: { fileName: file } });
|
collectAffectedTestFiles(file, collector);
|
||||||
|
if (this._testWatcher!.timer)
|
||||||
|
clearTimeout(this._testWatcher!.timer);
|
||||||
|
this._testWatcher!.timer = setTimeout(() => {
|
||||||
|
const fileNames = [...collector];
|
||||||
|
collector.clear();
|
||||||
|
this._dispatchEvent({ method: 'filesChanged', params: { fileNames } });
|
||||||
|
}, 250);
|
||||||
});
|
});
|
||||||
|
this._testWatcher = { watcher, watchedFiles, collector };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _stopTests() {
|
private async _stopTests() {
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ import { XtermWrapper } from '@web/components/xtermWrapper';
|
||||||
import { Expandable } from '@web/components/expandable';
|
import { Expandable } from '@web/components/expandable';
|
||||||
import { toggleTheme } from '@web/theme';
|
import { toggleTheme } from '@web/theme';
|
||||||
import { artifactsFolderName } from '@testIsomorphic/folders';
|
import { artifactsFolderName } from '@testIsomorphic/folders';
|
||||||
import { settings } from '@web/uiUtils';
|
import { settings, useSetting } from '@web/uiUtils';
|
||||||
|
|
||||||
let updateRootSuite: (config: FullConfig, rootSuite: Suite, progress: Progress | undefined) => void = () => {};
|
let updateRootSuite: (config: FullConfig, rootSuite: Suite, progress: Progress | undefined) => void = () => {};
|
||||||
let runWatchedTests = (fileName: string) => {};
|
let runWatchedTests = (fileNames: string[]) => {};
|
||||||
let xtermSize = { cols: 80, rows: 24 };
|
let xtermSize = { cols: 80, rows: 24 };
|
||||||
|
|
||||||
const xtermDataSource: XtermDataSource = {
|
const xtermDataSource: XtermDataSource = {
|
||||||
|
|
@ -72,6 +72,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
const [visibleTestIds, setVisibleTestIds] = React.useState<string[]>([]);
|
const [visibleTestIds, setVisibleTestIds] = React.useState<string[]>([]);
|
||||||
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
const [isLoading, setIsLoading] = React.useState<boolean>(false);
|
||||||
const [runningState, setRunningState] = React.useState<{ testIds: Set<string>, itemSelectedByUser?: boolean } | undefined>();
|
const [runningState, setRunningState] = React.useState<{ testIds: Set<string>, itemSelectedByUser?: boolean } | undefined>();
|
||||||
|
const [watchAll, setWatchAll] = useSetting<boolean>('watch-all', false);
|
||||||
|
|
||||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
|
@ -157,8 +158,9 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
<Toolbar noShadow={true}>
|
<Toolbar noShadow={true}>
|
||||||
<img src='icon-32x32.png' />
|
<img src='icon-32x32.png' />
|
||||||
<div className='section-title'>Playwright</div>
|
<div className='section-title'>Playwright</div>
|
||||||
<ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()} />
|
<ToolbarButton icon='color-mode' title='Toggle color mode' onClick={() => toggleTheme()} />
|
||||||
<ToolbarButton icon='refresh' title='Reload' onClick={() => reloadTests()} disabled={isRunningTest || isLoading}></ToolbarButton>
|
<ToolbarButton icon='refresh' title='Reload' onClick={() => reloadTests()} disabled={isRunningTest || isLoading}></ToolbarButton>
|
||||||
|
<ToolbarButton icon='eye' title='Watch all' toggled={watchAll} onClick={() => setWatchAll(!watchAll)}></ToolbarButton>
|
||||||
<ToolbarButton icon='terminal' title='Toggle output' toggled={isShowingOutput} onClick={() => { setIsShowingOutput(!isShowingOutput); }} />
|
<ToolbarButton icon='terminal' title='Toggle output' toggled={isShowingOutput} onClick={() => { setIsShowingOutput(!isShowingOutput); }} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<FiltersView
|
<FiltersView
|
||||||
|
|
@ -189,7 +191,8 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
runningState={runningState}
|
runningState={runningState}
|
||||||
runTests={runTests}
|
runTests={runTests}
|
||||||
onItemSelected={setSelectedItem}
|
onItemSelected={setSelectedItem}
|
||||||
setVisibleTestIds={setVisibleTestIds} />
|
setVisibleTestIds={setVisibleTestIds}
|
||||||
|
watchAll={watchAll} />
|
||||||
</div>
|
</div>
|
||||||
</SplitView>
|
</SplitView>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
@ -272,20 +275,25 @@ const TestList: React.FC<{
|
||||||
testModel: { rootSuite: Suite | undefined, config: FullConfig | undefined },
|
testModel: { rootSuite: Suite | undefined, config: FullConfig | undefined },
|
||||||
runTests: (testIds: string[]) => void,
|
runTests: (testIds: string[]) => void,
|
||||||
runningState?: { testIds: Set<string>, itemSelectedByUser?: boolean },
|
runningState?: { testIds: Set<string>, itemSelectedByUser?: boolean },
|
||||||
|
watchAll?: boolean,
|
||||||
setVisibleTestIds: (testIds: string[]) => void,
|
setVisibleTestIds: (testIds: string[]) => void,
|
||||||
onItemSelected: (item: { testCase?: TestCase, location?: Location }) => void,
|
onItemSelected: (item: { testCase?: TestCase, location?: Location }) => void,
|
||||||
}> = ({ statusFilters, projectFilters, filterText, testModel, runTests, runningState, onItemSelected, setVisibleTestIds }) => {
|
}> = ({ statusFilters, projectFilters, filterText, testModel, runTests, runningState, watchAll, onItemSelected, setVisibleTestIds }) => {
|
||||||
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
const [treeState, setTreeState] = React.useState<TreeState>({ expandedItems: new Map() });
|
||||||
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
const [selectedTreeItemId, setSelectedTreeItemId] = React.useState<string | undefined>();
|
||||||
const [watchedTreeIds, innerSetWatchedTreeIds] = React.useState<{ value: Set<string> }>({ value: new Set() });
|
const [watchedTreeIds, setWatchedTreeIds] = React.useState<{ value: Set<string> }>({ value: new Set() });
|
||||||
|
|
||||||
const { rootItem, treeItemMap } = React.useMemo(() => {
|
// Build the test tree.
|
||||||
|
const { rootItem, treeItemMap, fileNames } = React.useMemo(() => {
|
||||||
const rootItem = createTree(testModel.rootSuite, projectFilters);
|
const rootItem = createTree(testModel.rootSuite, projectFilters);
|
||||||
filterTree(rootItem, filterText, statusFilters);
|
filterTree(rootItem, filterText, statusFilters);
|
||||||
hideOnlyTests(rootItem);
|
hideOnlyTests(rootItem);
|
||||||
const treeItemMap = new Map<string, TreeItem>();
|
const treeItemMap = new Map<string, TreeItem>();
|
||||||
const visibleTestIds = new Set<string>();
|
const visibleTestIds = new Set<string>();
|
||||||
|
const fileNames = new Set<string>();
|
||||||
const visit = (treeItem: TreeItem) => {
|
const visit = (treeItem: TreeItem) => {
|
||||||
|
if (treeItem.kind === 'group' && treeItem.location.file)
|
||||||
|
fileNames.add(treeItem.location.file);
|
||||||
if (treeItem.kind === 'case')
|
if (treeItem.kind === 'case')
|
||||||
treeItem.tests.forEach(t => visibleTestIds.add(t.id));
|
treeItem.tests.forEach(t => visibleTestIds.add(t.id));
|
||||||
treeItem.children.forEach(visit);
|
treeItem.children.forEach(visit);
|
||||||
|
|
@ -293,11 +301,11 @@ const TestList: React.FC<{
|
||||||
};
|
};
|
||||||
visit(rootItem);
|
visit(rootItem);
|
||||||
setVisibleTestIds([...visibleTestIds]);
|
setVisibleTestIds([...visibleTestIds]);
|
||||||
return { rootItem, treeItemMap };
|
return { rootItem, treeItemMap, fileNames };
|
||||||
}, [filterText, testModel, statusFilters, projectFilters, setVisibleTestIds]);
|
}, [filterText, testModel, statusFilters, projectFilters, setVisibleTestIds]);
|
||||||
|
|
||||||
|
// Look for a first failure within the run batch to select it.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// Look for a first failure within the run batch to select it.
|
|
||||||
if (!runningState || runningState.itemSelectedByUser)
|
if (!runningState || runningState.itemSelectedByUser)
|
||||||
return;
|
return;
|
||||||
let selectedTreeItem: TreeItem | undefined;
|
let selectedTreeItem: TreeItem | undefined;
|
||||||
|
|
@ -318,6 +326,7 @@ const TestList: React.FC<{
|
||||||
setSelectedTreeItemId(selectedTreeItem.id);
|
setSelectedTreeItemId(selectedTreeItem.id);
|
||||||
}, [runningState, setSelectedTreeItemId, rootItem]);
|
}, [runningState, setSelectedTreeItemId, rootItem]);
|
||||||
|
|
||||||
|
// Compute selected item.
|
||||||
const { selectedTreeItem } = React.useMemo(() => {
|
const { selectedTreeItem } = React.useMemo(() => {
|
||||||
const selectedTreeItem = selectedTreeItemId ? treeItemMap.get(selectedTreeItemId) : undefined;
|
const selectedTreeItem = selectedTreeItemId ? treeItemMap.get(selectedTreeItemId) : undefined;
|
||||||
const location = selectedTreeItem?.location;
|
const location = selectedTreeItem?.location;
|
||||||
|
|
@ -330,27 +339,45 @@ const TestList: React.FC<{
|
||||||
return { selectedTreeItem };
|
return { selectedTreeItem };
|
||||||
}, [onItemSelected, selectedTreeItemId, treeItemMap]);
|
}, [onItemSelected, selectedTreeItemId, treeItemMap]);
|
||||||
|
|
||||||
const setWatchedTreeIds = (watchedTreeIds: Set<string>) => {
|
// Update watch all.
|
||||||
const fileNames = new Set<string>();
|
React.useEffect(() => {
|
||||||
for (const itemId of watchedTreeIds) {
|
if (watchAll) {
|
||||||
const treeItem = treeItemMap.get(itemId)!;
|
sendMessageNoReply('watch', { fileNames: [...fileNames] });
|
||||||
fileNames.add(fileNameForTreeItem(treeItem)!);
|
} else {
|
||||||
|
const fileNames = new Set<string>();
|
||||||
|
for (const itemId of watchedTreeIds.value) {
|
||||||
|
const treeItem = treeItemMap.get(itemId)!;
|
||||||
|
const fileName = fileNameForTreeItem(treeItem);
|
||||||
|
if (fileName)
|
||||||
|
fileNames.add(fileName);
|
||||||
|
}
|
||||||
|
sendMessageNoReply('watch', { fileNames: [...fileNames] });
|
||||||
}
|
}
|
||||||
sendMessageNoReply('watch', { fileNames: [...fileNames] });
|
}, [rootItem, fileNames, watchAll, watchedTreeIds, treeItemMap]);
|
||||||
innerSetWatchedTreeIds({ value: watchedTreeIds });
|
|
||||||
};
|
|
||||||
|
|
||||||
const runTreeItem = (treeItem: TreeItem) => {
|
const runTreeItem = (treeItem: TreeItem) => {
|
||||||
setSelectedTreeItemId(treeItem.id);
|
setSelectedTreeItemId(treeItem.id);
|
||||||
runTests(collectTestIds(treeItem));
|
runTests(collectTestIds(treeItem));
|
||||||
};
|
};
|
||||||
|
|
||||||
runWatchedTests = (fileName: string) => {
|
runWatchedTests = (fileNames: string[]) => {
|
||||||
const testIds: string[] = [];
|
const testIds: string[] = [];
|
||||||
for (const treeId of watchedTreeIds.value) {
|
const set = new Set(fileNames);
|
||||||
const treeItem = treeItemMap.get(treeId)!;
|
if (watchAll) {
|
||||||
if (fileNameForTreeItem(treeItem) === fileName)
|
const visit = (treeItem: TreeItem) => {
|
||||||
testIds.push(...collectTestIds(treeItem));
|
const fileName = fileNameForTreeItem(treeItem);
|
||||||
|
if (fileName && set.has(fileName))
|
||||||
|
testIds.push(...collectTestIds(treeItem));
|
||||||
|
treeItem.children.forEach(visit);
|
||||||
|
};
|
||||||
|
visit(rootItem);
|
||||||
|
} else {
|
||||||
|
for (const treeId of watchedTreeIds.value) {
|
||||||
|
const treeItem = treeItemMap.get(treeId)!;
|
||||||
|
const fileName = fileNameForTreeItem(treeItem);
|
||||||
|
if (fileName && set.has(fileName))
|
||||||
|
testIds.push(...collectTestIds(treeItem));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
runTests(testIds);
|
runTests(testIds);
|
||||||
};
|
};
|
||||||
|
|
@ -365,13 +392,13 @@ const TestList: React.FC<{
|
||||||
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
|
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
|
||||||
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton>
|
<ToolbarButton icon='play' title='Run' onClick={() => runTreeItem(treeItem)} disabled={!!runningState}></ToolbarButton>
|
||||||
<ToolbarButton icon='go-to-file' title='Open in VS Code' onClick={() => sendMessageNoReply('open', { location: locationToOpen(treeItem) })}></ToolbarButton>
|
<ToolbarButton icon='go-to-file' title='Open in VS Code' onClick={() => sendMessageNoReply('open', { location: locationToOpen(treeItem) })}></ToolbarButton>
|
||||||
<ToolbarButton icon='eye' title='Watch' onClick={() => {
|
{!watchAll && <ToolbarButton icon='eye' title='Watch' onClick={() => {
|
||||||
if (watchedTreeIds.value.has(treeItem.id))
|
if (watchedTreeIds.value.has(treeItem.id))
|
||||||
watchedTreeIds.value.delete(treeItem.id);
|
watchedTreeIds.value.delete(treeItem.id);
|
||||||
else
|
else
|
||||||
watchedTreeIds.value.add(treeItem.id);
|
watchedTreeIds.value.add(treeItem.id);
|
||||||
setWatchedTreeIds(watchedTreeIds.value);
|
setWatchedTreeIds({ ...watchedTreeIds });
|
||||||
}} toggled={watchedTreeIds.value.has(treeItem.id)}></ToolbarButton>
|
}} toggled={watchedTreeIds.value.has(treeItem.id)}></ToolbarButton>}
|
||||||
</div>;
|
</div>;
|
||||||
}}
|
}}
|
||||||
icon={treeItem => {
|
icon={treeItem => {
|
||||||
|
|
@ -532,8 +559,8 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.method === 'fileChanged') {
|
if (message.method === 'filesChanged') {
|
||||||
runWatchedTests(message.params.fileName);
|
runWatchedTests(message.params.fileNames);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,8 @@ export function dumpTestTree(page: Page): () => Promise<string> {
|
||||||
export const test = base
|
export const test = base
|
||||||
.extend<Fixtures>({
|
.extend<Fixtures>({
|
||||||
runUITest: async ({ childProcess, playwright, headless }, use, testInfo: TestInfo) => {
|
runUITest: async ({ childProcess, playwright, headless }, use, testInfo: TestInfo) => {
|
||||||
testInfo.slow();
|
if (process.env.CI)
|
||||||
|
testInfo.slow();
|
||||||
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
||||||
let testProcess: TestChildProcess | undefined;
|
let testProcess: TestChildProcess | undefined;
|
||||||
let browser: Browser | undefined;
|
let browser: Browser | undefined;
|
||||||
|
|
|
||||||
|
|
@ -57,3 +57,165 @@ test('should watch files', async ({ runUITest, writeFiles }) => {
|
||||||
✅ fails 👁 <=
|
✅ fails 👁 <=
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should watch e2e deps', async ({ runUITest, writeFiles }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
import { defineConfig } from '@playwright/test';
|
||||||
|
export default defineConfig({ testDir: 'tests' });
|
||||||
|
`,
|
||||||
|
'src/helper.ts': `
|
||||||
|
export const answer = 41;
|
||||||
|
`,
|
||||||
|
'tests/a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { answer } from '../src/helper';
|
||||||
|
test('answer', () => { expect(answer).toBe(42); });
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByText('answer').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'answer' }).getByTitle('Watch').click();
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ answer 👁 <=
|
||||||
|
`);
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'src/helper.ts': `
|
||||||
|
export const answer = 42;
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ✅ a.test.ts
|
||||||
|
✅ answer 👁 <=
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should batch watch updates', async ({ runUITest, writeFiles }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'b.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'c.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'd.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByText('a.test.ts').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'a.test.ts' }).getByTitle('Watch').click();
|
||||||
|
await page.getByText('b.test.ts').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'b.test.ts' }).getByTitle('Watch').click();
|
||||||
|
await page.getByText('c.test.ts').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'c.test.ts' }).getByTitle('Watch').click();
|
||||||
|
await page.getByText('d.test.ts').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'd.test.ts' }).getByTitle('Watch').click();
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts 👁
|
||||||
|
◯ test
|
||||||
|
▼ ◯ b.test.ts 👁
|
||||||
|
◯ test
|
||||||
|
▼ ◯ c.test.ts 👁
|
||||||
|
◯ test
|
||||||
|
▼ ◯ d.test.ts 👁 <=
|
||||||
|
◯ test
|
||||||
|
`);
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'b.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'c.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'd.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('status-line')).toHaveText('4/4 passed (100%)');
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ✅ a.test.ts 👁
|
||||||
|
✅ test
|
||||||
|
▼ ✅ b.test.ts 👁
|
||||||
|
✅ test
|
||||||
|
▼ ✅ c.test.ts 👁
|
||||||
|
✅ test
|
||||||
|
▼ ✅ d.test.ts 👁 <=
|
||||||
|
✅ test
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should watch all', async ({ runUITest, writeFiles }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'b.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'c.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'd.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ◯ c.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ◯ d.test.ts
|
||||||
|
◯ test
|
||||||
|
`);
|
||||||
|
await page.getByTitle('Watch all').click();
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
'd.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('status-line')).toHaveText('2/2 passed (100%)');
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ✅ a.test.ts
|
||||||
|
✅ test
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ◯ c.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ✅ d.test.ts
|
||||||
|
✅ test
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should watch new file', async ({ runUITest, writeFiles }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'a.test.ts': `import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByTitle('Watch all').click();
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ test
|
||||||
|
`);
|
||||||
|
|
||||||
|
// First time add file.
|
||||||
|
await writeFiles({
|
||||||
|
'b.test.ts': ` import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ test
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Second time run file.
|
||||||
|
await writeFiles({
|
||||||
|
'b.test.ts': ` import { test } from '@playwright/test'; test('test', () => {});`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 15000 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ test
|
||||||
|
▼ ✅ b.test.ts
|
||||||
|
✅ test
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue