Merge branch 'main' into ui-trace-not-found-path

This commit is contained in:
Simon Knott 2024-11-04 17:24:09 +01:00
commit b62c5694d4
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
7 changed files with 51 additions and 28 deletions

View file

@ -254,7 +254,7 @@ Note that by default `toPass` has timeout 0 and does not respect custom [expect
You can extend Playwright assertions by providing custom matchers. These matchers will be available on the `expect` object. You can extend Playwright assertions by providing custom matchers. These matchers will be available on the `expect` object.
In this example we add a custom `toHaveAmount` function. Custom matcher should return a `message` callback and a `pass` flag indicating whether the assertion passed. In this example we add a custom `toHaveAmount` function. Custom matcher should return a `pass` flag indicating whether the assertion passed, and a `message` callback that's used when the assertion fails.
```js title="fixtures.ts" ```js title="fixtures.ts"
import { expect as baseExpect } from '@playwright/test'; import { expect as baseExpect } from '@playwright/test';
@ -279,7 +279,7 @@ export const expect = baseExpect.extend({
? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + ? () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
'\n\n' + '\n\n' +
`Locator: ${locator}\n` + `Locator: ${locator}\n` +
`Expected: ${this.isNot ? 'not' : ''}${this.utils.printExpected(expected)}\n` + `Expected: not ${this.utils.printExpected(expected)}\n` +
(matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '') (matcherResult ? `Received: ${this.utils.printReceived(matcherResult.actual)}` : '')
: () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) + : () => this.utils.matcherHint(assertionName, undefined, undefined, { isNot: this.isNot }) +
'\n\n' + '\n\n' +

View file

@ -106,8 +106,17 @@ export async function installRootRedirect(server: HttpServer, traceUrls: string[
const params = new URLSearchParams(); const params = new URLSearchParams();
if (path.sep !== path.posix.sep) if (path.sep !== path.posix.sep)
params.set('pathSeparator', path.sep); params.set('pathSeparator', path.sep);
for (const traceUrl of traceUrls) for (const traceUrl of traceUrls) {
if (traceUrl.startsWith('http://') || traceUrl.startsWith('https://')) {
params.append('trace', traceUrl); params.append('trace', traceUrl);
continue;
}
// <testServerOrigin>/trace/file?path=/path/to/trace.zip
const url = new URL('/trace/file', server.urlPrefix('precise'));
url.searchParams.set('path', traceUrl);
params.append('trace', url.toString());
}
if (server.wsGuid()) if (server.wsGuid())
params.append('ws', server.wsGuid()!); params.append('ws', server.wsGuid()!);
if (options?.isServer) if (options?.isServer)

View file

@ -30,9 +30,8 @@ export class ZipTraceModelBackend implements TraceModelBackend {
constructor(traceURL: string, progress: Progress) { constructor(traceURL: string, progress: Progress) {
this._traceURL = traceURL; this._traceURL = traceURL;
zipjs.configure({ baseURL: self.location.href } as any);
this._zipReader = new zipjs.ZipReader( this._zipReader = new zipjs.ZipReader(
new zipjs.HttpReader(formatUrl(traceURL), { mode: 'cors', preventHeadRequest: true } as any), new zipjs.HttpReader(traceURL, { mode: 'cors', preventHeadRequest: true } as any),
{ useWebWorkers: false }); { useWebWorkers: false });
this._entriesPromise = this._zipReader.getEntries({ onprogress: progress }).then(entries => { this._entriesPromise = this._zipReader.getEntries({ onprogress: progress }).then(entries => {
const map = new Map<string, zip.Entry>(); const map = new Map<string, zip.Entry>();
@ -87,10 +86,9 @@ export class FetchTraceModelBackend implements TraceModelBackend {
constructor(traceURL: string) { constructor(traceURL: string) {
this._traceURL = traceURL; this._traceURL = traceURL;
this._entriesPromise = fetch('/trace/file?path=' + encodeURIComponent(traceURL)).then(async response => { this._entriesPromise = fetch(traceURL).then(async response => {
if (response.status === 404) if (response.status === 404)
throw new Error(`trace not found`); throw new Error(`trace not found`);
const json = await response.json(); const json = await response.json();
const entries = new Map<string, string>(); const entries = new Map<string, string>();
for (const entry of json.entries) for (const entry of json.entries)
@ -129,17 +127,12 @@ export class FetchTraceModelBackend implements TraceModelBackend {
private async _readEntry(entryName: string): Promise<Response | undefined> { private async _readEntry(entryName: string): Promise<Response | undefined> {
const entries = await this._entriesPromise; const entries = await this._entriesPromise;
const fileName = entries.get(entryName); const filePath = entries.get(entryName);
if (!fileName) if (!filePath)
return; return;
return fetch('/trace/file?path=' + encodeURIComponent(fileName));
const url = new URL(this.traceURL());
url.searchParams.set('path', filePath);
return fetch(url);
} }
} }
function formatUrl(trace: string) {
let url = trace.startsWith('http') || trace.startsWith('blob') ? trace : `file?path=${encodeURIComponent(trace)}`;
// Dropbox does not support cors.
if (url.startsWith('https://www.dropbox.com/'))
url = 'https://dl.dropboxusercontent.com/' + url.substring('https://www.dropbox.com/'.length);
return url;
}

View file

@ -21,6 +21,7 @@ import './embeddedWorkbenchLoader.css';
import { Workbench } from './workbench'; import { Workbench } from './workbench';
import { currentTheme, toggleTheme } from '@web/theme'; import { currentTheme, toggleTheme } from '@web/theme';
import type { SourceLocation } from './modelUtil'; import type { SourceLocation } from './modelUtil';
import { filePathToTraceURL } from './uiModeTraceView';
function openPage(url: string, target?: string) { function openPage(url: string, target?: string) {
if (url) if (url)
@ -40,7 +41,15 @@ export const EmbeddedWorkbenchLoader: React.FunctionComponent = () => {
React.useEffect(() => { React.useEffect(() => {
window.addEventListener('message', async ({ data: { method, params } }) => { window.addEventListener('message', async ({ data: { method, params } }) => {
if (method === 'loadTraceRequested') { if (method === 'loadTraceRequested') {
setTraceURLs(params.traceUrl ? [params.traceUrl] : []); if (params.traceUrl) {
// the param is called URL, but VS Code sends a path
const url = params.traceUrl.startsWith('http')
? params.traceUrl
: filePathToTraceURL(params.traceUrl).toString();
setTraceURLs([url]);
} else {
setTraceURLs([]);
}
setProcessingErrorMessage(null); setProcessingErrorMessage(null);
} else if (method === 'applyTheme') { } else if (method === 'applyTheme') {
if (currentTheme() !== params.theme) if (currentTheme() !== params.theme)

View file

@ -53,7 +53,7 @@ export const TraceView: React.FC<{
// Test finished. // Test finished.
const attachment = result && result.duration >= 0 && result.attachments.find(a => a.name === 'trace'); const attachment = result && result.duration >= 0 && result.attachments.find(a => a.name === 'trace');
if (attachment && attachment.path) { if (attachment && attachment.path) {
loadSingleTraceFile(attachment.path).then(model => { loadSingleTraceFile(filePathToTraceURL(attachment.path)).then(model => {
if (model) if (model)
setModel({ model, isLive: false }); setModel({ model, isLive: false });
}); });
@ -74,7 +74,7 @@ export const TraceView: React.FC<{
// Start polling running test. // Start polling running test.
pollTimer.current = setTimeout(async () => { pollTimer.current = setTimeout(async () => {
try { try {
const model = await loadSingleTraceFile(traceLocation); const model = await loadSingleTraceFile(filePathToTraceURL(traceLocation));
if (model) if (model)
setModel({ model, isLive: true }); setModel({ model, isLive: true });
} catch { } catch {
@ -109,9 +109,9 @@ const outputDirForTestCase = (testCase: reporterTypes.TestCase): string | undefi
return undefined; return undefined;
}; };
async function loadSingleTraceFile(url: string): Promise<MultiTraceModel | undefined> { async function loadSingleTraceFile(traceURL: URL): Promise<MultiTraceModel | undefined> {
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set('trace', url); params.set('trace', formatUrl(traceURL).toString());
params.set('limit', '1'); params.set('limit', '1');
const response = await fetch(`contexts?${params.toString()}`); const response = await fetch(`contexts?${params.toString()}`);
if (response.status === 404) if (response.status === 404)
@ -119,3 +119,17 @@ async function loadSingleTraceFile(url: string): Promise<MultiTraceModel | undef
const contextEntries = await response.json() as ContextEntry[]; const contextEntries = await response.json() as ContextEntry[];
return new MultiTraceModel(contextEntries); return new MultiTraceModel(contextEntries);
} }
function formatUrl(traceURL: URL) {
// Dropbox does not support cors.
if (traceURL.hostname === 'dropbox.com')
traceURL.hostname = 'dl.dropboxusercontent.com';
return traceURL;
}
export function filePathToTraceURL(path: string) {
const url = new URL('file', location.href);
url.searchParams.set('path', path);
return url;
}

View file

@ -104,8 +104,7 @@ it('should change document.activeElement', async ({ page, server }) => {
it('should not affect screenshots', async ({ page, server, browserName, headless, isWindows, channel }) => { it('should not affect screenshots', async ({ page, server, browserName, headless, isWindows, channel }) => {
it.skip(browserName === 'webkit' && isWindows && !headless, 'WebKit/Windows/headed has a larger minimal viewport. See https://github.com/microsoft/playwright/issues/22616'); it.skip(browserName === 'webkit' && isWindows && !headless, 'WebKit/Windows/headed has a larger minimal viewport. See https://github.com/microsoft/playwright/issues/22616');
it.skip(browserName === 'firefox' && !headless, 'Firefox headed produces a different image'); it.skip(browserName === 'firefox' && !headless, 'Firefox headed produces a different image');
// TODO: We want to see test results it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'https://github.com/microsoft/playwright/issues/33330');
// it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'https://github.com/microsoft/playwright/issues/33330');
const page2 = await page.context().newPage(); const page2 = await page.context().newPage();
await Promise.all([ await Promise.all([

View file

@ -23,8 +23,7 @@ browserTest.describe('page screenshot', () => {
browserTest.skip(({ browserName, headless }) => browserName === 'firefox' && !headless, 'Firefox headed produces a different image.'); browserTest.skip(({ browserName, headless }) => browserName === 'firefox' && !headless, 'Firefox headed produces a different image.');
browserTest('should run in parallel in multiple pages', async ({ server, contextFactory, browserName, channel }) => { browserTest('should run in parallel in multiple pages', async ({ server, contextFactory, browserName, channel }) => {
// TODO: We want to see test results browserTest.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'https://github.com/microsoft/playwright/issues/33330');
// browserTest.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'https://github.com/microsoft/playwright/issues/33330');
const context = await contextFactory(); const context = await contextFactory();
const N = 5; const N = 5;
const pages = await Promise.all(Array(N).fill(0).map(async () => { const pages = await Promise.all(Array(N).fill(0).map(async () => {