chore: use test project output dir when showing live trace (#21735)

This commit is contained in:
Pavel Feldman 2023-03-16 20:09:09 -07:00 committed by GitHub
parent eac910db82
commit e066ca0713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 37 deletions

View File

@ -43,6 +43,7 @@ export const ActionList: React.FC<ActionListProps> = ({
revealConsole = () => {},
}) => {
return <ActionListView
dataTestId='action-list'
items={actions}
id={action => action.callId}
selectedItem={selectedAction}

View File

@ -99,15 +99,6 @@ export const WatchModeView: React.FC<{}> = ({
setProgress(newProgress);
};
const outputDir = React.useMemo(() => {
let outputDir = '';
for (const p of rootSuite.value?.suites || []) {
outputDir = p.project()?.outputDir || '';
break;
}
return outputDir;
}, [rootSuite]);
const runTests = (testIds: string[]) => {
// Clear test results.
{
@ -130,6 +121,7 @@ export const WatchModeView: React.FC<{}> = ({
const isRunningTest = !!runningState;
const result = selectedTest?.results[0];
const outputDir = selectedTest ? outputDirForTestCase(selectedTest) : undefined;
return <div className='vbox watch-mode'>
<SplitView sidebarSize={250} orientation='horizontal' sidebarIsFirst={true}>
@ -388,7 +380,7 @@ const TestList: React.FC<{
};
const TraceView: React.FC<{
outputDir: string,
outputDir: string | undefined,
testCase: TestCase | undefined,
result: TestResult | undefined,
}> = ({ outputDir, testCase, result }) => {
@ -412,6 +404,11 @@ const TraceView: React.FC<{
return;
}
if (!outputDir) {
setModel(undefined);
return;
}
const traceLocation = `${outputDir}/${artifactsFolderName(result!.workerIndex)}/traces/${testCase?.id}.json`;
// Start polling running test.
pollTimer.current = setTimeout(async () => {
@ -535,6 +532,14 @@ const sendMessageNoReply = (method: string, params?: any) => {
});
};
const outputDirForTestCase = (testCase: TestCase): string | undefined => {
for (let suite: Suite | undefined = testCase.parent; suite; suite = suite.parent) {
if (suite.project())
return suite.project()?.outputDir;
}
return undefined;
};
const fileNameForTreeItem = (treeItem?: TreeItem): string | undefined => {
return treeItem?.location.file;
};

View File

@ -53,7 +53,7 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
}) => {
const codemirrorElement = React.useRef<HTMLDivElement>(null);
const [modulePromise] = React.useState<Promise<CodeMirror>>(import('./codeMirrorModule').then(m => m.default));
const codemirrorRef = React.useRef<{ cm: CodeMirror.Editor, highlight: SourceHighlight[], widgets: CodeMirror.LineWidget[] } | null>(null);
const codemirrorRef = React.useRef<{ cm: CodeMirror.Editor, highlight?: SourceHighlight[], widgets?: CodeMirror.LineWidget[] } | null>(null);
const [codemirror, setCodemirror] = React.useState<CodeMirror.Editor>();
React.useEffect(() => {
@ -92,7 +92,7 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
lineNumbers,
lineWrapping: wrapLines,
});
codemirrorRef.current = { cm, highlight: [], widgets: [] };
codemirrorRef.current = { cm };
setCodemirror(cm);
return cm;
})();
@ -108,43 +108,47 @@ export const CodeMirrorWrapper: React.FC<SourceProps> = ({
codemirror.on('change', (codemirror as any)[listenerSymbol]);
}
let valueChanged = false;
if (codemirror.getValue() !== text) {
codemirror.setValue(text);
valueChanged = true;
if (focusOnChange) {
codemirror.execCommand('selectAll');
codemirror.focus();
}
}
// Line highlight.
for (const h of codemirrorRef.current!.highlight)
codemirror.removeLineClass(h.line - 1, 'wrap');
for (const h of highlight || [])
codemirror.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`);
codemirrorRef.current!.highlight = highlight || [];
if (valueChanged || JSON.stringify(highlight) !== JSON.stringify(codemirrorRef.current!.highlight)) {
// Line highlight.
for (const h of codemirrorRef.current!.highlight || [])
codemirror.removeLineClass(h.line - 1, 'wrap');
for (const h of highlight || [])
codemirror.addLineClass(h.line - 1, 'wrap', `source-line-${h.type}`);
// Error widgets.
for (const w of codemirrorRef.current!.widgets)
codemirror.removeLineWidget(w);
const widgets: CodeMirror.LineWidget[] = [];
for (const h of highlight || []) {
if (h.type !== 'error')
continue;
// Error widgets.
for (const w of codemirrorRef.current!.widgets || [])
codemirror.removeLineWidget(w);
const widgets: CodeMirror.LineWidget[] = [];
for (const h of highlight || []) {
if (h.type !== 'error')
continue;
const line = codemirrorRef.current?.cm.getLine(h.line - 1);
if (line) {
const underlineWidgetElement = document.createElement('div');
underlineWidgetElement.className = 'source-line-error-underline';
underlineWidgetElement.innerHTML = '&nbsp;'.repeat(line.length || 1);
widgets.push(codemirror.addLineWidget(h.line, underlineWidgetElement, { above: true, coverGutter: false }));
const line = codemirrorRef.current?.cm.getLine(h.line - 1);
if (line) {
const underlineWidgetElement = document.createElement('div');
underlineWidgetElement.className = 'source-line-error-underline';
underlineWidgetElement.innerHTML = '&nbsp;'.repeat(line.length || 1);
widgets.push(codemirror.addLineWidget(h.line, underlineWidgetElement, { above: true, coverGutter: false }));
}
const errorWidgetElement = document.createElement('div');
errorWidgetElement.innerHTML = ansi2htmlMarkup(h.message || '');
errorWidgetElement.className = 'source-line-error-widget';
widgets.push(codemirror.addLineWidget(h.line, errorWidgetElement, { above: true, coverGutter: false }));
}
const errorWidgetElement = document.createElement('div');
errorWidgetElement.innerHTML = ansi2htmlMarkup(h.message || '');
errorWidgetElement.className = 'source-line-error-widget';
widgets.push(codemirror.addLineWidget(h.line, errorWidgetElement, { above: true, coverGutter: false }));
codemirrorRef.current!.highlight = highlight;
codemirrorRef.current!.widgets = widgets;
}
codemirrorRef.current!.widgets = widgets;
if (revealLine && codemirrorRef.current!.cm.lineCount() >= revealLine)
codemirror.scrollIntoView({ line: revealLine - 1, ch: 0 }, 50);

View File

@ -0,0 +1,111 @@
/**
* 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 { ManualPromise } from '../../packages/playwright-core/lib/utils/manualPromise';
import { test, expect } from './ui-mode-fixtures';
test.describe.configure({ mode: 'parallel' });
test('should update trace live', async ({ runUITest, server }) => {
const onePromise = new ManualPromise();
server.setRoute('/one.html', async (req, res) => {
await onePromise;
res.end('<html>One</html>');
});
const twoPromise = new ManualPromise();
server.setRoute('/two.html', async (req, res) => {
await twoPromise;
res.end('<html>Two</html>');
});
const page = await runUITest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('live test', async ({ page }) => {
await page.goto('${server.PREFIX}/one.html');
await page.goto('${server.PREFIX}/two.html');
});
`,
});
// Start test.
await page.getByText('live test').dblclick();
// It should halt on loading one.html.
const listItem = page.getByTestId('action-list').getByRole('listitem');
await expect(
listItem,
'action list'
).toHaveText([
/browserContext.newPage[\d.]+m?s/,
/page.gotohttp:\/\/localhost:\d+\/one.html/
]);
await expect(
listItem.locator(':scope.selected'),
'last action to be selected'
).toHaveText(/page.goto/);
await expect(
listItem.locator(':scope.selected .codicon.codicon-loading'),
'spinner'
).toBeVisible();
await expect(
page.locator('.CodeMirror .source-line-running'),
'check source tab',
).toHaveText(/4 await page.goto\('http:\/\/localhost:\d+\/one.html/);
// Unlock the navigation step.
onePromise.resolve();
await expect(
page.frameLocator('id=snapshot').locator('body'),
'verify snapshot'
).toHaveText('One');
await expect(listItem).toHaveText([
/browserContext.newPage[\d.]+m?s/,
/page.gotohttp:\/\/localhost:\d+\/one.html[\d.]+m?s/,
/page.gotohttp:\/\/localhost:\d+\/two.html/
]);
await expect(
listItem.locator(':scope.selected'),
'last action to be selected'
).toHaveText(/page.goto/);
await expect(
listItem.locator(':scope.selected .codicon.codicon-loading'),
'spinner'
).toBeVisible();
await expect(
page.locator('.CodeMirror .source-line-running'),
'check source tab',
).toHaveText(/5 await page.goto\('http:\/\/localhost:\d+\/two.html/);
// Unlock the navigation step.
twoPromise.resolve();
await expect(
page.frameLocator('id=snapshot').locator('body'),
'verify snapshot'
).toHaveText('Two');
await expect(listItem).toHaveText([
/browserContext.newPage[\d.]+m?s/,
/page.gotohttp:\/\/localhost:\d+\/one.html[\d.]+m?s/,
/page.gotohttp:\/\/localhost:\d+\/two.html[\d.]+m?s/
]);
});