chore(ui): start adding ui mode tests (2) (#21608)
This commit is contained in:
parent
17498c30dc
commit
1d870ac407
|
|
@ -87,7 +87,8 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||||
|
|
||||||
if (traceViewerBrowser === 'chromium')
|
if (traceViewerBrowser === 'chromium')
|
||||||
await installAppIcon(page);
|
await installAppIcon(page);
|
||||||
await syncLocalStorageWithSettings(page, 'traceviewer');
|
if (!isUnderTest())
|
||||||
|
await syncLocalStorageWithSettings(page, 'traceviewer');
|
||||||
|
|
||||||
const params = traceUrls.map(t => `trace=${t}`);
|
const params = traceUrls.map(t => `trace=${t}`);
|
||||||
if (isUnderTest()) {
|
if (isUnderTest()) {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class UIMode {
|
||||||
projectDirs.add(p.testDir);
|
projectDirs.add(p.testDir);
|
||||||
let coalescingTimer: NodeJS.Timeout | undefined;
|
let coalescingTimer: NodeJS.Timeout | undefined;
|
||||||
const watcher = chokidar.watch([...projectDirs], { ignoreInitial: true, persistent: true }).on('all', async event => {
|
const watcher = chokidar.watch([...projectDirs], { ignoreInitial: true, persistent: true }).on('all', async event => {
|
||||||
if (event !== 'add' && event !== 'change')
|
if (event !== 'add' && event !== 'change' && event !== 'unlink')
|
||||||
return;
|
return;
|
||||||
if (coalescingTimer)
|
if (coalescingTimer)
|
||||||
clearTimeout(coalescingTimer);
|
clearTimeout(coalescingTimer);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view-entry:not(.highlighted) .toolbar-button:not(.toggled) {
|
.list-view-entry:not(.highlighted):not(.selected) .toolbar-button:not(.toggled) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
<div className='vbox watch-mode-sidebar'>
|
<div className='vbox watch-mode-sidebar'>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<div className='section-title' style={{ cursor: 'pointer' }} onClick={() => setSettingsVisible(false)}>Tests</div>
|
<div className='section-title' style={{ cursor: 'pointer' }} onClick={() => setSettingsVisible(false)}>Tests</div>
|
||||||
<ToolbarButton icon='play' title='Run' onClick={() => runTests(visibleTestIds)} disabled={isRunningTest}></ToolbarButton>
|
<ToolbarButton icon='play' title='Run all' onClick={() => runTests(visibleTestIds)} disabled={isRunningTest}></ToolbarButton>
|
||||||
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
<ToolbarButton icon='debug-stop' title='Stop' onClick={() => sendMessageNoReply('stop')} disabled={!isRunningTest}></ToolbarButton>
|
||||||
<ToolbarButton icon='refresh' title='Reload' onClick={() => refreshRootSuite(true)} disabled={isRunningTest}></ToolbarButton>
|
<ToolbarButton icon='refresh' title='Reload' onClick={() => refreshRootSuite(true)} disabled={isRunningTest}></ToolbarButton>
|
||||||
<div className='spacer'></div>
|
<div className='spacer'></div>
|
||||||
|
|
@ -202,7 +202,7 @@ export const WatchModeView: React.FC<{}> = ({
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeListView = TreeView<TreeItem>;
|
const TestTreeView = TreeView<TreeItem>;
|
||||||
|
|
||||||
export const TestList: React.FC<{
|
export const TestList: React.FC<{
|
||||||
projects: Map<string, boolean>,
|
projects: Map<string, boolean>,
|
||||||
|
|
@ -299,7 +299,7 @@ export const TestList: React.FC<{
|
||||||
if (!isVisible)
|
if (!isVisible)
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|
||||||
return <TreeListView
|
return <TestTreeView
|
||||||
treeState={treeState}
|
treeState={treeState}
|
||||||
setTreeState={setTreeState}
|
setTreeState={setTreeState}
|
||||||
rootItem={rootItem}
|
rootItem={rootItem}
|
||||||
|
|
@ -336,6 +336,7 @@ export const TestList: React.FC<{
|
||||||
runningState.itemSelectedByUser = true;
|
runningState.itemSelectedByUser = true;
|
||||||
setSelectedTreeItemId(treeItem.id);
|
setSelectedTreeItemId(treeItem.id);
|
||||||
}}
|
}}
|
||||||
|
autoExpandDeep={!!filterText}
|
||||||
noItemsMessage='No tests' />;
|
noItemsMessage='No tests' />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export type TreeViewProps<T> = {
|
||||||
dataTestId?: string,
|
dataTestId?: string,
|
||||||
treeState: TreeState,
|
treeState: TreeState,
|
||||||
setTreeState: (treeState: TreeState) => void,
|
setTreeState: (treeState: TreeState) => void,
|
||||||
|
autoExpandDeep?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeListView = ListView<TreeItem>;
|
const TreeListView = ListView<TreeItem>;
|
||||||
|
|
@ -57,12 +58,13 @@ export function TreeView<T extends TreeItem>({
|
||||||
setTreeState,
|
setTreeState,
|
||||||
noItemsMessage,
|
noItemsMessage,
|
||||||
dataTestId,
|
dataTestId,
|
||||||
|
autoExpandDeep,
|
||||||
}: TreeViewProps<T>) {
|
}: TreeViewProps<T>) {
|
||||||
const treeItems = React.useMemo(() => {
|
const treeItems = React.useMemo(() => {
|
||||||
for (let item: TreeItem | undefined = selectedItem?.parent; item; item = item.parent)
|
for (let item: TreeItem | undefined = selectedItem?.parent; item; item = item.parent)
|
||||||
treeState.expandedItems.set(item.id, true);
|
treeState.expandedItems.set(item.id, true);
|
||||||
return flattenTree<T>(rootItem, treeState.expandedItems);
|
return flattenTree<T>(rootItem, treeState.expandedItems, autoExpandDeep);
|
||||||
}, [rootItem, selectedItem, treeState]);
|
}, [rootItem, selectedItem, treeState, autoExpandDeep]);
|
||||||
|
|
||||||
return <TreeListView
|
return <TreeListView
|
||||||
items={[...treeItems.keys()]}
|
items={[...treeItems.keys()]}
|
||||||
|
|
@ -126,12 +128,12 @@ type TreeItemData = {
|
||||||
parent: TreeItem | null,
|
parent: TreeItem | null,
|
||||||
};
|
};
|
||||||
|
|
||||||
function flattenTree<T extends TreeItem>(rootItem: T, expandedItems: Map<string, boolean | undefined>): Map<T, TreeItemData> {
|
function flattenTree<T extends TreeItem>(rootItem: T, expandedItems: Map<string, boolean | undefined>, autoExpandDeep?: boolean): Map<T, TreeItemData> {
|
||||||
const result = new Map<T, TreeItemData>();
|
const result = new Map<T, TreeItemData>();
|
||||||
const appendChildren = (parent: T, depth: number) => {
|
const appendChildren = (parent: T, depth: number) => {
|
||||||
for (const item of parent.children as T[]) {
|
for (const item of parent.children as T[]) {
|
||||||
const expandState = expandedItems.get(item.id);
|
const expandState = expandedItems.get(item.id);
|
||||||
const autoExpandMatches = depth === 0 && result.size < 25 && expandState !== false;
|
const autoExpandMatches = (autoExpandDeep || depth === 0) && result.size < 25 && expandState !== false;
|
||||||
const expanded = item.children.length ? expandState || autoExpandMatches : undefined;
|
const expanded = item.children.length ? expandState || autoExpandMatches : undefined;
|
||||||
result.set(item, { depth, expanded, parent: rootItem === parent ? null : parent });
|
result.set(item, { depth, expanded, parent: rootItem === parent ? null : parent });
|
||||||
if (expanded)
|
if (expanded)
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,7 @@ export type RunOptions = {
|
||||||
};
|
};
|
||||||
type Fixtures = {
|
type Fixtures = {
|
||||||
writeFiles: (files: Files) => Promise<string>;
|
writeFiles: (files: Files) => Promise<string>;
|
||||||
|
deleteFile: (file: string) => Promise<void>;
|
||||||
runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<RunResult>;
|
runInlineTest: (files: Files, params?: Params, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<RunResult>;
|
||||||
runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
runWatchTest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<TestChildProcess>;
|
||||||
runTSC: (files: Files) => Promise<TSCResult>;
|
runTSC: (files: Files) => Promise<TSCResult>;
|
||||||
|
|
@ -237,6 +238,13 @@ export const test = base
|
||||||
await use(files => writeFiles(testInfo, files, false));
|
await use(files => writeFiles(testInfo, files, false));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteFile: async ({}, use, testInfo) => {
|
||||||
|
await use(async file => {
|
||||||
|
const baseDir = testInfo.outputPath();
|
||||||
|
await fs.promises.unlink(path.join(baseDir, file));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => {
|
runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => {
|
||||||
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-'));
|
||||||
await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => {
|
await use(async (files: Files, params: Params = {}, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => {
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,16 @@ export function dumpTestTree(page: Page): () => Promise<string> {
|
||||||
return ' ';
|
return ' ';
|
||||||
if (icon === 'circle-outline')
|
if (icon === 'circle-outline')
|
||||||
return '◯';
|
return '◯';
|
||||||
|
if (icon === 'circle-slash')
|
||||||
|
return '⊘';
|
||||||
if (icon === 'check')
|
if (icon === 'check')
|
||||||
return '✅';
|
return '✅';
|
||||||
if (icon === 'error')
|
if (icon === 'error')
|
||||||
return '❌';
|
return '❌';
|
||||||
|
if (icon === 'eye')
|
||||||
|
return '👁';
|
||||||
|
if (icon === 'loading')
|
||||||
|
return '↻';
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,8 +58,9 @@ export function dumpTestTree(page: Page): () => Promise<string> {
|
||||||
const treeIcon = iconName(iconElements[0]);
|
const treeIcon = iconName(iconElements[0]);
|
||||||
const statusIcon = iconName(iconElements[1]);
|
const statusIcon = iconName(iconElements[1]);
|
||||||
const indent = listItem.querySelectorAll('.list-view-indent').length;
|
const indent = listItem.querySelectorAll('.list-view-indent').length;
|
||||||
|
const watch = listItem.querySelector('.toolbar-button.eye.toggled') ? ' 👁' : '';
|
||||||
const selected = listItem.classList.contains('selected') ? ' <=' : '';
|
const selected = listItem.classList.contains('selected') ? ' <=' : '';
|
||||||
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + listItem.textContent + selected);
|
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + listItem.textContent + watch + selected);
|
||||||
}
|
}
|
||||||
return '\n' + result.join('\n') + '\n ';
|
return '\n' + result.join('\n') + '\n ';
|
||||||
});
|
});
|
||||||
|
|
|
||||||
99
tests/playwright-test/ui-mode-test-run.spec.ts
Normal file
99
tests/playwright-test/ui-mode-test-run.spec.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* 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, dumpTestTree } from './ui-mode-fixtures';
|
||||||
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
|
const basicTestTree = {
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => { expect(1).toBe(2); });
|
||||||
|
test.describe('suite', () => {
|
||||||
|
test('inner passes', () => {});
|
||||||
|
test('inner fails', () => { expect(1).toBe(2); });
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'b.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => { expect(1).toBe(2); });
|
||||||
|
`,
|
||||||
|
'c.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test.skip('skipped', () => {});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should run visible', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.getByTitle('Run all').click();
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ❌ a.test.ts
|
||||||
|
✅ passes
|
||||||
|
❌ fails <=
|
||||||
|
► ❌ suite
|
||||||
|
▼ ❌ b.test.ts
|
||||||
|
✅ passes
|
||||||
|
❌ fails
|
||||||
|
▼ ✅ c.test.ts
|
||||||
|
✅ passes
|
||||||
|
⊘ skipped
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should run on double click', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => { expect(1).toBe(2); });
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByText('passes').dblclick();
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
✅ passes <=
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should run on Enter', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => { expect(1).toBe(2); });
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByText('fails').click();
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ❌ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
❌ fails <=
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
@ -37,7 +37,7 @@ const basicTestTree = {
|
||||||
|
|
||||||
test('should list tests', async ({ runUITest }) => {
|
test('should list tests', async ({ runUITest }) => {
|
||||||
const page = await runUITest(basicTestTree);
|
const page = await runUITest(basicTestTree);
|
||||||
await expect.poll(dumpTestTree(page)).toBe(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails
|
◯ fails
|
||||||
|
|
@ -51,7 +51,7 @@ test('should list tests', async ({ runUITest }) => {
|
||||||
test('should traverse up/down', async ({ runUITest }) => {
|
test('should traverse up/down', async ({ runUITest }) => {
|
||||||
const page = await runUITest(basicTestTree);
|
const page = await runUITest(basicTestTree);
|
||||||
await page.getByText('a.test.ts').click();
|
await page.getByText('a.test.ts').click();
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts <=
|
▼ ◯ a.test.ts <=
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails
|
◯ fails
|
||||||
|
|
@ -59,14 +59,14 @@ test('should traverse up/down', async ({ runUITest }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await page.keyboard.press('ArrowDown');
|
await page.keyboard.press('ArrowDown');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes <=
|
◯ passes <=
|
||||||
◯ fails
|
◯ fails
|
||||||
► ◯ suite
|
► ◯ suite
|
||||||
`);
|
`);
|
||||||
await page.keyboard.press('ArrowDown');
|
await page.keyboard.press('ArrowDown');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails <=
|
◯ fails <=
|
||||||
|
|
@ -74,7 +74,7 @@ test('should traverse up/down', async ({ runUITest }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await page.keyboard.press('ArrowUp');
|
await page.keyboard.press('ArrowUp');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes <=
|
◯ passes <=
|
||||||
◯ fails
|
◯ fails
|
||||||
|
|
@ -87,7 +87,7 @@ test('should expand / collapse groups', async ({ runUITest }) => {
|
||||||
|
|
||||||
await page.getByText('suite').click();
|
await page.getByText('suite').click();
|
||||||
await page.keyboard.press('ArrowRight');
|
await page.keyboard.press('ArrowRight');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails
|
◯ fails
|
||||||
|
|
@ -97,7 +97,7 @@ test('should expand / collapse groups', async ({ runUITest }) => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await page.keyboard.press('ArrowLeft');
|
await page.keyboard.press('ArrowLeft');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts
|
▼ ◯ a.test.ts
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails
|
◯ fails
|
||||||
|
|
@ -106,14 +106,25 @@ test('should expand / collapse groups', async ({ runUITest }) => {
|
||||||
|
|
||||||
await page.getByText('passes').first().click();
|
await page.getByText('passes').first().click();
|
||||||
await page.keyboard.press('ArrowLeft');
|
await page.keyboard.press('ArrowLeft');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
▼ ◯ a.test.ts <=
|
▼ ◯ a.test.ts <=
|
||||||
◯ passes
|
◯ passes
|
||||||
◯ fails
|
◯ fails
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await page.keyboard.press('ArrowLeft');
|
await page.keyboard.press('ArrowLeft');
|
||||||
await expect.poll(dumpTestTree(page)).toContain(`
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
► ◯ a.test.ts <=
|
► ◯ a.test.ts <=
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should filter by title', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await page.getByPlaceholder('Filter').fill('inner');
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
▼ ◯ suite
|
||||||
|
◯ inner passes
|
||||||
|
◯ inner fails
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
|
||||||
170
tests/playwright-test/ui-mode-test-update.spec.ts
Normal file
170
tests/playwright-test/ui-mode-test-update.spec.ts
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
/**
|
||||||
|
* 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, dumpTestTree } from './ui-mode-fixtures';
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
|
const basicTestTree = {
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => {});
|
||||||
|
test.describe('suite', () => {
|
||||||
|
test('inner passes', () => {});
|
||||||
|
test('inner fails', () => {});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'b.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => {});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should pick new / deleted files', async ({ runUITest, writeFiles, deleteFile }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'c.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => {});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
▼ ◯ c.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await deleteFile('a.test.ts');
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
▼ ◯ c.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pick new / deleted tests', async ({ runUITest, writeFiles, deleteFile }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('new', () => {});
|
||||||
|
test('fails', () => {});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ new
|
||||||
|
◯ fails
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await deleteFile('a.test.ts');
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('new', () => {});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ new
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pick new / deleted nested tests', async ({ runUITest, writeFiles, deleteFile }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.getByText('suite').click();
|
||||||
|
await page.keyboard.press('ArrowRight');
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
▼ ◯ suite <=
|
||||||
|
◯ inner passes
|
||||||
|
◯ inner fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test.describe('suite', () => {
|
||||||
|
test('inner new', () => {});
|
||||||
|
test('inner fails', () => {});
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
▼ ◯ suite <=
|
||||||
|
◯ inner new
|
||||||
|
◯ inner fails
|
||||||
|
`);
|
||||||
|
});
|
||||||
53
tests/playwright-test/ui-mode-test-watch.spec.ts
Normal file
53
tests/playwright-test/ui-mode-test-watch.spec.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* 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, dumpTestTree } from './ui-mode-fixtures';
|
||||||
|
|
||||||
|
test.describe.configure({ mode: 'parallel' });
|
||||||
|
|
||||||
|
test('should watch files', async ({ runUITest, writeFiles }) => {
|
||||||
|
const page = await runUITest({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => { expect(1).toBe(2); });
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByText('fails').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'fails' }).getByTitle('Watch').click();
|
||||||
|
await page.getByRole('listitem').filter({ hasText: 'fails' }).getByTitle('Run').click();
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ❌ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
❌ fails 👁 <=
|
||||||
|
`);
|
||||||
|
|
||||||
|
await writeFiles({
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('passes', () => {});
|
||||||
|
test('fails', () => { expect(1).toBe(1); });
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect.poll(dumpTestTree(page), { timeout: 0 }).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
✅ fails 👁 <=
|
||||||
|
`);
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue