chore(ui): start adding ui mode tests (#21601)
This commit is contained in:
parent
493171cb6b
commit
a12e909a40
|
|
@ -63,8 +63,6 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||||
'--window-size=1280,800',
|
'--window-size=1280,800',
|
||||||
'--test-type=',
|
'--test-type=',
|
||||||
] : [];
|
] : [];
|
||||||
if (isUnderTest())
|
|
||||||
args.push(`--remote-debugging-port=0`);
|
|
||||||
|
|
||||||
const context = await traceViewerPlaywright[traceViewerBrowser as 'chromium'].launchPersistentContext(serverSideCallMetadata(), '', {
|
const context = await traceViewerPlaywright[traceViewerBrowser as 'chromium'].launchPersistentContext(serverSideCallMetadata(), '', {
|
||||||
// TODO: store language in the trace.
|
// TODO: store language in the trace.
|
||||||
|
|
@ -74,7 +72,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||||
ignoreDefaultArgs: ['--enable-automation'],
|
ignoreDefaultArgs: ['--enable-automation'],
|
||||||
headless,
|
headless,
|
||||||
colorScheme: 'no-override',
|
colorScheme: 'no-override',
|
||||||
useWebSocket: isUnderTest()
|
useWebSocket: isUnderTest(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const controller = new ProgressController(serverSideCallMetadata(), context._browser);
|
const controller = new ProgressController(serverSideCallMetadata(), context._browser);
|
||||||
|
|
@ -84,6 +82,9 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
|
||||||
await context.extendInjectedScript(consoleApiSource.source);
|
await context.extendInjectedScript(consoleApiSource.source);
|
||||||
const [page] = context.pages();
|
const [page] = context.pages();
|
||||||
|
|
||||||
|
if (isUnderTest())
|
||||||
|
process.stderr.write('DevTools listening on: ' + context._browser.options.wsEndpoint + '\n');
|
||||||
|
|
||||||
if (traceViewerBrowser === 'chromium')
|
if (traceViewerBrowser === 'chromium')
|
||||||
await installAppIcon(page);
|
await installAppIcon(page);
|
||||||
await syncLocalStorageWithSettings(page, 'traceviewer');
|
await syncLocalStorageWithSettings(page, 'traceviewer');
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ export function debugMode() {
|
||||||
return debugEnv ? 'inspector' : '';
|
return debugEnv ? 'inspector' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let _isUnderTest = false;
|
let _isUnderTest = !!process.env.PWTEST_UNDER_TEST;
|
||||||
export function setUnderTest() {
|
export function setUnderTest() {
|
||||||
_isUnderTest = true;
|
_isUnderTest = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { showTraceViewer } from 'playwright-core/lib/server';
|
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 { 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, dependenciesForTestFile } from '../common/compilationCache';
|
||||||
import type { FullConfigInternal } from '../common/types';
|
import type { FullConfigInternal } from '../common/types';
|
||||||
|
|
@ -53,15 +53,6 @@ class UIMode {
|
||||||
config._internal.configCLIOverrides.use.trace = { mode: 'on', sources: false };
|
config._internal.configCLIOverrides.use.trace = { mode: 'on', sources: false };
|
||||||
|
|
||||||
this._originalStderr = process.stderr.write.bind(process.stderr);
|
this._originalStderr = process.stderr.write.bind(process.stderr);
|
||||||
process.stdout.write = (chunk: string | Buffer) => {
|
|
||||||
this._dispatchEvent({ method: 'stdio', params: chunkToPayload('stdout', chunk) });
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
process.stderr.write = (chunk: string | Buffer) => {
|
|
||||||
this._dispatchEvent({ method: 'stdio', params: chunkToPayload('stderr', chunk) });
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
this._installGlobalWatcher();
|
this._installGlobalWatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +92,15 @@ class UIMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
async showUI() {
|
async showUI() {
|
||||||
this._page = await showTraceViewer([], 'chromium', { app: 'watch.html' });
|
this._page = await showTraceViewer([], 'chromium', { app: 'watch.html', headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== '1' });
|
||||||
|
process.stdout.write = (chunk: string | Buffer) => {
|
||||||
|
this._dispatchEvent({ method: 'stdio', params: chunkToPayload('stdout', chunk) });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
process.stderr.write = (chunk: string | Buffer) => {
|
||||||
|
this._dispatchEvent({ method: 'stdio', params: chunkToPayload('stderr', chunk) });
|
||||||
|
return true;
|
||||||
|
};
|
||||||
const exitPromise = new ManualPromise();
|
const exitPromise = new ManualPromise();
|
||||||
this._page.on('close', () => exitPromise.resolve());
|
this._page.on('close', () => exitPromise.resolve());
|
||||||
let queue = Promise.resolve();
|
let queue = Promise.resolve();
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,7 @@ export const TestList: React.FC<{
|
||||||
treeState={treeState}
|
treeState={treeState}
|
||||||
setTreeState={setTreeState}
|
setTreeState={setTreeState}
|
||||||
rootItem={rootItem}
|
rootItem={rootItem}
|
||||||
|
dataTestId='test-tree'
|
||||||
render={treeItem => {
|
render={treeItem => {
|
||||||
return <div className='hbox watch-mode-list-item'>
|
return <div className='hbox watch-mode-list-item'>
|
||||||
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
|
<div className='watch-mode-list-item-title'>{treeItem.title}</div>
|
||||||
|
|
@ -653,9 +654,12 @@ function createTree(rootSuite: Suite | undefined, projects: Map<string, boolean>
|
||||||
visitSuite(projectSuite.title, projectSuite, rootItem);
|
visitSuite(projectSuite.title, projectSuite, rootItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
const propagateStatus = (treeItem: TreeItem) => {
|
const sortAndPropagateStatus = (treeItem: TreeItem) => {
|
||||||
for (const child of treeItem.children)
|
for (const child of treeItem.children)
|
||||||
propagateStatus(child);
|
sortAndPropagateStatus(child);
|
||||||
|
|
||||||
|
if (treeItem.kind === 'group' && treeItem.parent)
|
||||||
|
treeItem.children.sort((a, b) => a.location.line - b.location.line);
|
||||||
|
|
||||||
let allPassed = treeItem.children.length > 0;
|
let allPassed = treeItem.children.length > 0;
|
||||||
let allSkipped = treeItem.children.length > 0;
|
let allSkipped = treeItem.children.length > 0;
|
||||||
|
|
@ -678,7 +682,7 @@ function createTree(rootSuite: Suite | undefined, projects: Map<string, boolean>
|
||||||
else if (allPassed)
|
else if (allPassed)
|
||||||
treeItem.status = 'passed';
|
treeItem.status = 'passed';
|
||||||
};
|
};
|
||||||
propagateStatus(rootItem);
|
sortAndPropagateStatus(rootItem);
|
||||||
return rootItem;
|
return rootItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,10 @@
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-view-indent {
|
||||||
|
min-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.list-view-content:focus .list-view-entry.selected {
|
.list-view-content:focus .list-view-entry.selected {
|
||||||
background-color: var(--vscode-list-activeSelectionBackground);
|
background-color: var(--vscode-list-activeSelectionBackground);
|
||||||
color: var(--vscode-list-activeSelectionForeground);
|
color: var(--vscode-list-activeSelectionForeground);
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export function ListView<T>({
|
||||||
onHighlighted?.(highlightedItem);
|
onHighlighted?.(highlightedItem);
|
||||||
}, [onHighlighted, highlightedItem]);
|
}, [onHighlighted, highlightedItem]);
|
||||||
|
|
||||||
return <div className='list-view vbox' data-testid={dataTestId}>
|
return <div className='list-view vbox' role='list' data-testid={dataTestId}>
|
||||||
<div
|
<div
|
||||||
className='list-view-content'
|
className='list-view-content'
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|
@ -115,14 +115,15 @@ export function ListView<T>({
|
||||||
const rendered = render(item);
|
const rendered = render(item);
|
||||||
return <div
|
return <div
|
||||||
key={id?.(item) || index}
|
key={id?.(item) || index}
|
||||||
|
role='listitem'
|
||||||
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix}
|
className={'list-view-entry' + selectedSuffix + highlightedSuffix + errorSuffix}
|
||||||
onClick={() => onSelected?.(item)}
|
onClick={() => onSelected?.(item)}
|
||||||
onMouseEnter={() => setHighlightedItem(item)}
|
onMouseEnter={() => setHighlightedItem(item)}
|
||||||
onMouseLeave={() => setHighlightedItem(undefined)}
|
onMouseLeave={() => setHighlightedItem(undefined)}
|
||||||
>
|
>
|
||||||
{indentation ? <div style={{ minWidth: indentation * 16 }}></div> : undefined}
|
{indentation ? new Array(indentation).fill(0).map(() => <div className='list-view-indent'></div>) : undefined}
|
||||||
{icon && <div
|
{icon && <div
|
||||||
className={'codicon ' + (icon(item) || 'blank')}
|
className={'codicon ' + (icon(item) || 'codicon-blank')}
|
||||||
style={{ minWidth: 16, marginRight: 4 }}
|
style={{ minWidth: 16, marginRight: 4 }}
|
||||||
onDoubleClick={e => {
|
onDoubleClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ export function TreeView<T extends TreeItem>({
|
||||||
treeState,
|
treeState,
|
||||||
setTreeState,
|
setTreeState,
|
||||||
noItemsMessage,
|
noItemsMessage,
|
||||||
|
dataTestId,
|
||||||
}: 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)
|
||||||
|
|
@ -66,6 +67,7 @@ export function TreeView<T extends TreeItem>({
|
||||||
return <TreeListView
|
return <TreeListView
|
||||||
items={[...treeItems.keys()]}
|
items={[...treeItems.keys()]}
|
||||||
id={item => item.id}
|
id={item => item.id}
|
||||||
|
dataTestId={dataTestId}
|
||||||
render={item => {
|
render={item => {
|
||||||
const rendered = render(item as T);
|
const rendered = render(item as T);
|
||||||
return <>
|
return <>
|
||||||
|
|
|
||||||
|
|
@ -83,18 +83,24 @@ export class TestChildProcess {
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
if (!this.process.killed)
|
if (!this.process.killed)
|
||||||
this._killProcessGroup();
|
this._killProcessGroup('SIGINT');
|
||||||
return this.exited;
|
return this.exited;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _killProcessGroup() {
|
async kill() {
|
||||||
|
if (!this.process.killed)
|
||||||
|
this._killProcessGroup('SIGKILL');
|
||||||
|
return this.exited;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _killProcessGroup(signal: 'SIGINT' | 'SIGKILL') {
|
||||||
if (!this.process.pid || this.process.killed)
|
if (!this.process.pid || this.process.killed)
|
||||||
return;
|
return;
|
||||||
try {
|
try {
|
||||||
if (process.platform === 'win32')
|
if (process.platform === 'win32')
|
||||||
execSync(`taskkill /pid ${this.process.pid} /T /F /FI "MEMUSAGE gt 0"`, { stdio: 'ignore' });
|
execSync(`taskkill /pid ${this.process.pid} /T /F /FI "MEMUSAGE gt 0"`, { stdio: 'ignore' });
|
||||||
else
|
else
|
||||||
process.kill(-this.process.pid, 'SIGKILL');
|
process.kill(-this.process.pid, signal);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// the process might have already stopped
|
// the process might have already stopped
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import type { TestInfo } from './stable-test-runner';
|
||||||
import { expect } from './stable-test-runner';
|
import { expect } from './stable-test-runner';
|
||||||
import { test as base } from './stable-test-runner';
|
import { test as base } from './stable-test-runner';
|
||||||
|
|
||||||
const removeFolderAsync = promisify(rimraf);
|
export const removeFolderAsync = promisify(rimraf);
|
||||||
|
|
||||||
export type CliRunResult = {
|
export type CliRunResult = {
|
||||||
exitCode: number,
|
exitCode: number,
|
||||||
|
|
@ -54,10 +54,10 @@ type TSCResult = {
|
||||||
exitCode: number;
|
exitCode: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Files = { [key: string]: string | Buffer };
|
export type Files = { [key: string]: string | Buffer };
|
||||||
type Params = { [key: string]: string | number | boolean | string[] };
|
type Params = { [key: string]: string | number | boolean | string[] };
|
||||||
|
|
||||||
async function writeFiles(testInfo: TestInfo, files: Files, initial: boolean) {
|
export async function writeFiles(testInfo: TestInfo, files: Files, initial: boolean) {
|
||||||
const baseDir = testInfo.outputPath();
|
const baseDir = testInfo.outputPath();
|
||||||
|
|
||||||
if (initial && !Object.keys(files).some(name => name.includes('package.json'))) {
|
if (initial && !Object.keys(files).some(name => name.includes('package.json'))) {
|
||||||
|
|
@ -76,7 +76,7 @@ async function writeFiles(testInfo: TestInfo, files: Files, initial: boolean) {
|
||||||
return baseDir;
|
return baseDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cliEntrypoint = path.join(__dirname, '../../packages/playwright-core/cli.js');
|
export const cliEntrypoint = path.join(__dirname, '../../packages/playwright-core/cli.js');
|
||||||
|
|
||||||
async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions): Promise<RunResult> {
|
async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], baseDir: string, params: any, env: NodeJS.ProcessEnv, options: RunOptions): Promise<RunResult> {
|
||||||
const paramList: string[] = [];
|
const paramList: string[] = [];
|
||||||
|
|
@ -190,7 +190,7 @@ async function runPlaywrightCommand(childProcess: CommonFixtures['childProcess']
|
||||||
return { exitCode, output: testProcess.output.toString() };
|
return { exitCode, output: testProcess.output.toString() };
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
export function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
return {
|
return {
|
||||||
...process.env,
|
...process.env,
|
||||||
// BEGIN: Reserved CI
|
// BEGIN: Reserved CI
|
||||||
|
|
@ -216,7 +216,7 @@ function cleanEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunOptions = {
|
export type RunOptions = {
|
||||||
sendSIGINTAfter?: number;
|
sendSIGINTAfter?: number;
|
||||||
additionalArgs?: string[];
|
additionalArgs?: string[];
|
||||||
cwd?: string,
|
cwd?: string,
|
||||||
|
|
@ -254,7 +254,7 @@ export const test = base
|
||||||
testProcess = watchPlaywrightTest(childProcess, baseDir, { ...env, PWTEST_CACHE_DIR: cacheDir }, options);
|
testProcess = watchPlaywrightTest(childProcess, baseDir, { ...env, PWTEST_CACHE_DIR: cacheDir }, options);
|
||||||
return testProcess;
|
return testProcess;
|
||||||
});
|
});
|
||||||
await testProcess?.close();
|
await testProcess?.kill();
|
||||||
await removeFolderAsync(cacheDir);
|
await removeFolderAsync(cacheDir);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* 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 * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { TestChildProcess } from '../config/commonFixtures';
|
||||||
|
import { cleanEnv, cliEntrypoint, removeFolderAsync, test as base, writeFiles } from './playwright-test-fixtures';
|
||||||
|
import type { Files, RunOptions } from './playwright-test-fixtures';
|
||||||
|
import type { Browser, Page, TestInfo } from './stable-test-runner';
|
||||||
|
|
||||||
|
type Fixtures = {
|
||||||
|
runUITest: (files: Files, env?: NodeJS.ProcessEnv, options?: RunOptions) => Promise<Page>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function dumpTestTree(page: Page): () => Promise<string> {
|
||||||
|
return () => page.getByTestId('test-tree').evaluate(async treeElement => {
|
||||||
|
function iconName(iconElement: Element): string {
|
||||||
|
const icon = iconElement.className.replace('codicon codicon-', '');
|
||||||
|
if (icon === 'chevron-right')
|
||||||
|
return '►';
|
||||||
|
if (icon === 'chevron-down')
|
||||||
|
return '▼';
|
||||||
|
if (icon === 'blank')
|
||||||
|
return ' ';
|
||||||
|
if (icon === 'circle-outline')
|
||||||
|
return '◯';
|
||||||
|
if (icon === 'check')
|
||||||
|
return '✅';
|
||||||
|
if (icon === 'error')
|
||||||
|
return '❌';
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: string[] = [];
|
||||||
|
const listItems = treeElement.querySelectorAll('[role=listitem]');
|
||||||
|
for (const listItem of listItems) {
|
||||||
|
const iconElements = listItem.querySelectorAll('.codicon');
|
||||||
|
const treeIcon = iconName(iconElements[0]);
|
||||||
|
const statusIcon = iconName(iconElements[1]);
|
||||||
|
const indent = listItem.querySelectorAll('.list-view-indent').length;
|
||||||
|
const selected = listItem.classList.contains('selected') ? ' <=' : '';
|
||||||
|
result.push(' ' + ' '.repeat(indent) + treeIcon + ' ' + statusIcon + ' ' + listItem.textContent + selected);
|
||||||
|
}
|
||||||
|
return '\n' + result.join('\n') + '\n ';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = base
|
||||||
|
.extend<Fixtures>({
|
||||||
|
runUITest: async ({ childProcess, playwright, headless }, use, testInfo: TestInfo) => {
|
||||||
|
const cacheDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-test-cache-'));
|
||||||
|
let testProcess: TestChildProcess | undefined;
|
||||||
|
let browser: Browser | undefined;
|
||||||
|
await use(async (files: Files, env: NodeJS.ProcessEnv = {}, options: RunOptions = {}) => {
|
||||||
|
const baseDir = await writeFiles(testInfo, files, true);
|
||||||
|
testProcess = childProcess({
|
||||||
|
command: ['node', cliEntrypoint, 'ui', ...(options.additionalArgs || [])],
|
||||||
|
env: {
|
||||||
|
...cleanEnv(env),
|
||||||
|
PWTEST_UNDER_TEST: '1',
|
||||||
|
PWTEST_CACHE_DIR: cacheDir,
|
||||||
|
PWTEST_HEADED_FOR_TEST: headless ? '0' : '1',
|
||||||
|
},
|
||||||
|
cwd: options.cwd ? path.resolve(baseDir, options.cwd) : baseDir,
|
||||||
|
});
|
||||||
|
await testProcess.waitForOutput('DevTools listening on');
|
||||||
|
const line = testProcess.output.split('\n').find(l => l.includes('DevTools listening on'));
|
||||||
|
const wsEndpoint = line!.split(' ')[3];
|
||||||
|
browser = await playwright.chromium.connectOverCDP(wsEndpoint);
|
||||||
|
const [context] = browser.contexts();
|
||||||
|
const [page] = context.pages();
|
||||||
|
return page;
|
||||||
|
});
|
||||||
|
await browser?.close();
|
||||||
|
await testProcess?.close();
|
||||||
|
await removeFolderAsync(cacheDir);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export { expect } from './stable-test-runner';
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* 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 list tests', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await expect.poll(dumpTestTree(page)).toBe(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
▼ ◯ b.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should traverse up/down', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
await page.getByText('a.test.ts').click();
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts <=
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowDown');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes <=
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
`);
|
||||||
|
await page.keyboard.press('ArrowDown');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails <=
|
||||||
|
► ◯ suite
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowUp');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes <=
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should expand / collapse groups', async ({ runUITest }) => {
|
||||||
|
const page = await runUITest(basicTestTree);
|
||||||
|
|
||||||
|
await page.getByText('suite').click();
|
||||||
|
await page.keyboard.press('ArrowRight');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
▼ ◯ suite <=
|
||||||
|
◯ inner passes
|
||||||
|
◯ inner fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowLeft');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
► ◯ suite <=
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.getByText('passes').first().click();
|
||||||
|
await page.keyboard.press('ArrowLeft');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
▼ ◯ a.test.ts <=
|
||||||
|
◯ passes
|
||||||
|
◯ fails
|
||||||
|
`);
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowLeft');
|
||||||
|
await expect.poll(dumpTestTree(page)).toContain(`
|
||||||
|
► ◯ a.test.ts <=
|
||||||
|
`);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue