Merge branch 'main' into ui-trace-not-found-path
This commit is contained in:
commit
b62c5694d4
|
|
@ -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' +
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
params.append('trace', traceUrl);
|
if (traceUrl.startsWith('http://') || traceUrl.startsWith('https://')) {
|
||||||
|
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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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([
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue