2021-06-29 22:35:50 -07:00
/ * *
* 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 .
* /
2022-04-06 13:57:14 -08:00
import type { TraceViewerFixtures } from '../config/traceViewerFixtures' ;
import { traceViewerFixtures } from '../config/traceViewerFixtures' ;
2021-11-08 18:03:10 -08:00
import fs from 'fs' ;
2021-06-29 22:35:50 -07:00
import path from 'path' ;
2024-06-10 03:44:52 -06:00
import { pathToFileURL } from 'url' ;
2022-04-05 15:10:12 -08:00
import { expect , playwrightTest } from '../config/browserTest' ;
2023-06-09 07:18:13 -07:00
import type { FrameLocator } from '@playwright/test' ;
2021-06-29 22:35:50 -07:00
2022-04-05 15:10:12 -08:00
const test = playwrightTest . extend < TraceViewerFixtures > ( traceViewerFixtures ) ;
2021-06-29 22:35:50 -07:00
2021-10-26 12:45:53 -08:00
test . skip ( ( { trace } ) = > trace === 'on' ) ;
2023-08-04 14:59:48 -07:00
test . skip ( ( { mode } ) = > mode . startsWith ( 'service' ) ) ;
2021-12-22 11:17:43 -08:00
test . slow ( ) ;
2021-10-13 19:32:23 -07:00
2021-06-29 22:35:50 -07:00
let traceFile : string ;
2021-09-07 15:23:13 -07:00
test . beforeAll ( async function recordTrace ( { browser , browserName , browserType , server } , workerInfo ) {
2024-07-25 17:14:46 +02:00
const context = await browser . newContext ( {
baseURL : 'https://example.com' ,
} ) ;
2021-11-01 18:57:29 -08:00
await context . tracing . start ( { name : 'test' , screenshots : true , snapshots : true , sources : true } ) ;
2021-06-29 22:35:50 -07:00
const page = await context . newPage ( ) ;
2023-07-10 12:56:56 -07:00
await page . goto ( ` data:text/html,<!DOCTYPE html><html>Hello world</html> ` ) ;
await page . setContent ( '<!DOCTYPE html><button>Click</button>' ) ;
2021-09-28 16:02:34 -07:00
await expect ( page . locator ( 'button' ) ) . toHaveText ( 'Click' ) ;
2023-05-30 17:45:48 +02:00
await expect ( page . getByTestId ( 'amazing-btn' ) ) . toBeHidden ( ) ;
await expect ( page . getByTestId ( /amazing-btn-regex/ ) ) . toBeHidden ( ) ;
2021-07-02 16:45:09 -07:00
await page . evaluate ( ( { a } ) = > {
2021-07-01 14:31:20 -07:00
console . log ( 'Info' ) ;
console . warn ( 'Warning' ) ;
console . error ( 'Error' ) ;
2021-08-31 21:49:08 -07:00
return new Promise ( f = > {
// Generate exception.
2024-05-31 14:44:26 -07:00
window . builtinSetTimeout ( ( ) = > {
2021-08-31 21:49:08 -07:00
// And then resolve.
2024-05-31 14:44:26 -07:00
window . builtinSetTimeout ( ( ) = > f ( 'return ' + a ) , 0 ) ;
2021-08-31 21:49:08 -07:00
throw new Error ( 'Unhandled exception' ) ;
} , 0 ) ;
} ) ;
2021-07-02 16:45:09 -07:00
} , { a : 'paramA' , b : 4 } ) ;
2021-08-16 17:06:38 -07:00
2022-02-17 22:12:42 +01:00
await page . evaluate ( ( ) = > 1 + 1 , null ) ;
2021-08-16 17:06:38 -07:00
async function doClick() {
2022-10-18 22:23:40 -04:00
await page . getByText ( 'Click' ) . click ( ) ;
2021-08-16 17:06:38 -07:00
}
await doClick ( ) ;
2021-06-30 17:56:48 -07:00
await Promise . all ( [
page . waitForNavigation ( ) ,
2022-09-08 11:31:02 -07:00
page . waitForResponse ( server . PREFIX + '/frames/frame.html' ) ,
2021-09-07 15:23:13 -07:00
page . waitForTimeout ( 200 ) . then ( ( ) = > page . goto ( server . PREFIX + '/frames/frame.html' ) )
2021-06-30 17:56:48 -07:00
] ) ;
2021-08-05 16:04:09 -07:00
await page . setViewportSize ( { width : 500 , height : 600 } ) ;
2021-08-16 17:06:38 -07:00
// Go through instrumentation to exercise reentrant stack traces.
2023-04-28 08:57:43 -07:00
const csi = {
2024-05-28 14:29:57 -07:00
runBeforeCloseBrowserContext : async ( ) = > {
2023-04-28 08:57:43 -07:00
await page . hover ( 'body' ) ;
await page . close ( ) ;
2024-09-17 15:32:30 +02:00
traceFile = path . join ( workerInfo . project . outputDir , String ( workerInfo . workerIndex ) , browserName , 'trace.zip' ) ;
2023-04-28 08:57:43 -07:00
await context . tracing . stop ( { path : traceFile } ) ;
}
2021-08-16 17:06:38 -07:00
} ;
2023-04-28 08:57:43 -07:00
( browserType as any ) . _instrumentation . addListener ( csi ) ;
2021-08-16 17:06:38 -07:00
await context . close ( ) ;
2023-04-28 08:57:43 -07:00
( browserType as any ) . _instrumentation . removeListener ( csi ) ;
2021-06-29 22:35:50 -07:00
} ) ;
test ( 'should show empty trace viewer' , async ( { showTraceViewer } , testInfo ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ testInfo . outputPath ( ) ] ) ;
2021-11-03 10:44:50 -07:00
await expect ( traceViewer . page ) . toHaveTitle ( 'Playwright Trace Viewer' ) ;
2021-06-29 22:35:50 -07:00
} ) ;
2022-09-27 12:45:42 -07:00
test ( 'should open two trace viewers' , async ( { showTraceViewer } , testInfo ) = > {
2023-01-27 23:20:25 +01:00
const port = testInfo . workerIndex + 48321 ;
const traceViewer1 = await showTraceViewer ( [ testInfo . outputPath ( ) ] , { host : 'localhost' , port } ) ;
2022-09-27 12:45:42 -07:00
await expect ( traceViewer1 . page ) . toHaveTitle ( 'Playwright Trace Viewer' ) ;
2023-01-27 23:20:25 +01:00
const traceViewer2 = await showTraceViewer ( [ testInfo . outputPath ( ) ] , { host : 'localhost' , port } ) ;
2022-09-27 12:45:42 -07:00
await expect ( traceViewer2 . page ) . toHaveTitle ( 'Playwright Trace Viewer' ) ;
} ) ;
2023-01-27 23:20:25 +01:00
test ( 'should open trace viewer on specific host' , async ( { showTraceViewer } , testInfo ) = > {
const traceViewer = await showTraceViewer ( [ testInfo . outputPath ( ) ] , { host : '127.0.0.1' } ) ;
await expect ( traceViewer . page ) . toHaveTitle ( 'Playwright Trace Viewer' ) ;
await expect ( traceViewer . page ) . toHaveURL ( /127.0.0.1/ ) ;
} ) ;
2021-06-29 22:35:50 -07:00
test ( 'should open simple trace viewer' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-09-03 12:34:47 -07:00
await expect ( traceViewer . actionTitles ) . toHaveText ( [
2021-11-02 11:16:12 -08:00
/browserContext.newPage/ ,
2023-07-10 12:56:56 -07:00
/page.gotodata:text\/html,<!DOCTYPE html><html>Hello world<\/html>/ ,
2021-11-02 11:16:12 -08:00
/page.setContent/ ,
2022-10-18 22:23:40 -04:00
/expect.toHaveTextlocator\('button'\)/ ,
2023-05-30 17:45:48 +02:00
/expect.toBeHiddengetByTestId\('amazing-btn'\)/ ,
/expect.toBeHiddengetByTestId\(\/amazing-btn-regex\/\)/ ,
2021-11-02 11:16:12 -08:00
/page.evaluate/ ,
2022-02-17 22:12:42 +01:00
/page.evaluate/ ,
2022-10-18 22:23:40 -04:00
/locator.clickgetByText\('Click'\)/ ,
2021-11-02 11:16:12 -08:00
/page.waitForNavigation/ ,
2022-09-08 11:31:02 -07:00
/page.waitForResponse/ ,
2021-11-02 11:16:12 -08:00
/page.waitForTimeout/ ,
/page.gotohttp:\/\/localhost:\d+\/frames\/frame.html/ ,
/page.setViewportSize/ ,
2021-06-30 17:56:48 -07:00
] ) ;
2021-06-29 22:35:50 -07:00
} ) ;
2024-07-22 08:16:25 -07:00
test ( 'should complain about newer version of trace in old viewer' , async ( { showTraceViewer , asset } , testInfo ) = > {
const traceViewer = await showTraceViewer ( [ asset ( 'trace-from-the-future.zip' ) ] ) ;
await expect ( traceViewer . page . getByText ( 'The trace was created by a newer version of Playwright and is not supported by this version of the viewer.' ) ) . toBeVisible ( ) ;
} ) ;
2024-09-17 11:14:15 -07:00
test ( 'should properly synchronize local and remote time' , async ( { showTraceViewer , asset } , testInfo ) = > {
const traceViewer = await showTraceViewer ( [ asset ( 'trace-remote-time-diff.zip' ) ] ) ;
// The total duration should be sub 10s, rather than 16h.
await expect ( traceViewer . page . locator ( '.timeline-time' ) . last ( ) ) . toHaveText ( '8.5s' ) ;
} ) ;
2021-07-02 14:33:38 -07:00
test ( 'should contain action info' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2022-10-18 22:23:40 -04:00
await traceViewer . selectAction ( 'locator.click' ) ;
2023-09-01 20:12:05 -07:00
await traceViewer . page . getByText ( 'Log' , { exact : true } ) . click ( ) ;
2023-10-26 14:45:15 -07:00
await expect ( traceViewer . logLines ) . toContainText ( [
/\d+m?sattempting click action/ ,
/\d+m?s click action done/ ,
] ) ;
2021-06-29 22:35:50 -07:00
} ) ;
2021-06-30 17:56:48 -07:00
2023-08-16 16:30:17 -07:00
test ( 'should render network bars' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
} ) ;
await expect ( traceViewer . page . locator ( '.timeline-bar.network' ) ) . toHaveCount ( 1 ) ;
2021-06-30 17:56:48 -07:00
} ) ;
2021-07-01 14:31:20 -07:00
test ( 'should render console' , async ( { showTraceViewer , browserName } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-08-16 17:06:38 -07:00
await traceViewer . showConsoleTab ( ) ;
2021-07-01 14:31:20 -07:00
2023-07-31 11:24:04 -07:00
await expect ( traceViewer . consoleLineMessages . nth ( 0 ) ) . toHaveText ( 'Info' ) ;
await expect ( traceViewer . consoleLineMessages . nth ( 1 ) ) . toHaveText ( 'Warning' ) ;
await expect ( traceViewer . consoleLineMessages . nth ( 2 ) ) . toHaveText ( 'Error' ) ;
await expect ( traceViewer . consoleLineMessages . nth ( 3 ) ) . toHaveText ( 'Unhandled exception' ) ;
2023-10-10 09:14:58 -07:00
// Browsers can insert more messages between these two.
await expect ( traceViewer . consoleLineMessages . filter ( { hasText : 'Cheers!' } ) ) . toBeVisible ( ) ;
2023-07-31 11:24:04 -07:00
const icons = traceViewer . consoleLines . locator ( '.codicon' ) ;
2023-08-21 16:05:27 -07:00
await expect . soft ( icons . nth ( 0 ) ) . toHaveClass ( 'codicon codicon-browser status-none' ) ;
await expect . soft ( icons . nth ( 1 ) ) . toHaveClass ( 'codicon codicon-browser status-warning' ) ;
await expect . soft ( icons . nth ( 2 ) ) . toHaveClass ( 'codicon codicon-browser status-error' ) ;
await expect . soft ( icons . nth ( 3 ) ) . toHaveClass ( 'codicon codicon-browser status-error' ) ;
2023-10-10 09:14:58 -07:00
// Browsers can insert more messages between these two.
await expect . soft ( traceViewer . consoleLines . filter ( { hasText : 'Cheers!' } ) . locator ( '.codicon' ) ) . toHaveClass ( 'codicon codicon-browser status-none' ) ;
2021-08-31 21:49:08 -07:00
await expect ( traceViewer . consoleStacks . first ( ) ) . toContainText ( 'Error: Unhandled exception' ) ;
2023-07-10 12:56:56 -07:00
await traceViewer . selectAction ( 'page.evaluate' ) ;
2023-07-31 11:24:04 -07:00
const listViews = traceViewer . page . locator ( '.console-tab' ) . locator ( '.list-view-entry' ) ;
2023-08-16 16:30:17 -07:00
await expect ( listViews . nth ( 0 ) ) . toHaveClass ( 'list-view-entry' ) ;
await expect ( listViews . nth ( 1 ) ) . toHaveClass ( 'list-view-entry warning' ) ;
await expect ( listViews . nth ( 2 ) ) . toHaveClass ( 'list-view-entry error' ) ;
await expect ( listViews . nth ( 3 ) ) . toHaveClass ( 'list-view-entry error' ) ;
2023-10-10 09:14:58 -07:00
// Browsers can insert more messages between these two.
await expect ( listViews . filter ( { hasText : 'Cheers!' } ) ) . toHaveClass ( 'list-view-entry' ) ;
2021-07-01 14:31:20 -07:00
} ) ;
2021-07-01 20:46:56 -07:00
test ( 'should open console errors on click' , async ( { showTraceViewer , browserName } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-07-01 20:46:56 -07:00
expect ( await traceViewer . actionIconsText ( 'page.evaluate' ) ) . toEqual ( [ '2' , '1' ] ) ;
2021-08-05 16:04:09 -07:00
expect ( await traceViewer . page . isHidden ( '.console-tab' ) ) . toBeTruthy ( ) ;
2021-07-01 20:46:56 -07:00
await ( await traceViewer . actionIcons ( 'page.evaluate' ) ) . click ( ) ;
2021-08-05 16:04:09 -07:00
expect ( await traceViewer . page . waitForSelector ( '.console-tab' ) ) . toBeTruthy ( ) ;
2021-07-01 20:46:56 -07:00
} ) ;
2021-07-02 16:45:09 -07:00
2023-02-16 07:59:21 -08:00
test ( 'should show params and return value' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-08-05 16:04:09 -07:00
await traceViewer . selectAction ( 'page.evaluate' ) ;
2021-09-03 12:34:47 -07:00
await expect ( traceViewer . callLines ) . toHaveText ( [
2021-11-02 11:16:12 -08:00
/page.evaluate/ ,
2023-02-16 07:59:21 -08:00
/wall time:[0-9/:,APM ]+/ ,
/duration:[\d]+ms/ ,
/expression:"\({↵ a↵ }\) => {↵ console\.log\(\'Info\'\);↵ console\.warn\(\'Warning\'\);↵ console/ ,
'isFunction:true' ,
'arg:{"a":"paramA","b":4}' ,
'value:"return paramA"'
2021-07-02 16:45:09 -07:00
] ) ;
2023-01-05 16:59:50 -08:00
await traceViewer . selectAction ( ` locator('button') ` ) ;
await expect ( traceViewer . callLines ) . toContainText ( [
/expect.toHaveText/ ,
2023-02-16 07:59:21 -08:00
/wall time:[0-9/:,APM ]+/ ,
/duration:[\d]+ms/ ,
/locator:locator\('button'\)/ ,
/expression:"to.have.text"/ ,
/timeout:10000/ ,
/matches:true/ ,
/received:"Click"/ ,
2023-01-05 16:59:50 -08:00
] ) ;
2021-07-02 16:45:09 -07:00
} ) ;
2021-08-05 16:04:09 -07:00
2022-02-17 22:12:42 +01:00
test ( 'should show null as a param' , async ( { showTraceViewer , browserName } ) = > {
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
await traceViewer . selectAction ( 'page.evaluate' , 1 ) ;
await expect ( traceViewer . callLines ) . toHaveText ( [
/page.evaluate/ ,
2023-02-16 07:59:21 -08:00
/wall time:[0-9/:,APM ]+/ ,
/duration:[\d]+ms/ ,
'expression:"() => 1 + 1"' ,
'isFunction:true' ,
'arg:null' ,
'value:2'
2022-02-17 22:12:42 +01:00
] ) ;
} ) ;
2021-09-23 11:40:55 -07:00
test ( 'should have correct snapshot size' , async ( { showTraceViewer } , testInfo ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-08-05 16:04:09 -07:00
await traceViewer . selectAction ( 'page.setViewport' ) ;
await traceViewer . selectSnapshot ( 'Before' ) ;
2021-09-23 11:40:55 -07:00
await expect ( traceViewer . snapshotContainer ) . toHaveCSS ( 'width' , '1280px' ) ;
await expect ( traceViewer . snapshotContainer ) . toHaveCSS ( 'height' , '720px' ) ;
2021-08-05 16:04:09 -07:00
await traceViewer . selectSnapshot ( 'After' ) ;
2021-09-23 11:40:55 -07:00
await expect ( traceViewer . snapshotContainer ) . toHaveCSS ( 'width' , '500px' ) ;
await expect ( traceViewer . snapshotContainer ) . toHaveCSS ( 'height' , '600px' ) ;
2021-08-05 16:04:09 -07:00
} ) ;
2021-08-16 17:06:38 -07:00
test ( 'should have correct stack trace' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-08-16 17:06:38 -07:00
2022-10-18 22:23:40 -04:00
await traceViewer . selectAction ( 'locator.click' ) ;
2021-08-16 17:06:38 -07:00
await traceViewer . showSourceTab ( ) ;
2021-09-27 11:14:35 -07:00
await expect ( traceViewer . stackFrames ) . toContainText ( [
/doClick\s+trace-viewer.spec.ts\s+:\d+/ ,
/recordTrace\s+trace-viewer.spec.ts\s+:\d+/ ,
] , { useInnerText : true } ) ;
2021-08-16 17:06:38 -07:00
} ) ;
2021-09-07 15:23:13 -07:00
test ( 'should have network requests' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-09-07 15:23:13 -07:00
await traceViewer . selectAction ( 'http://localhost' ) ;
await traceViewer . showNetworkTab ( ) ;
2024-01-10 15:28:33 -08:00
await expect ( traceViewer . networkRequests ) . toContainText ( [ /frame.htmlGET200text\/html/ ] ) ;
await expect ( traceViewer . networkRequests ) . toContainText ( [ /style.cssGET200text\/css/ ] ) ;
2024-02-01 13:44:26 -08:00
await expect ( traceViewer . networkRequests ) . toContainText ( [ /404GET404text\/plain/ ] ) ;
2024-01-10 15:28:33 -08:00
await expect ( traceViewer . networkRequests ) . toContainText ( [ /script.jsGET200application\/javascript/ ] ) ;
2024-02-01 13:44:26 -08:00
await expect ( traceViewer . networkRequests . filter ( { hasText : '404' } ) ) . toHaveCSS ( 'background-color' , 'rgb(242, 222, 222)' ) ;
2021-09-07 15:23:13 -07:00
} ) ;
2021-10-12 13:42:50 -08:00
2024-08-06 23:52:35 +02:00
test ( 'should filter network requests by resource type' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
server . setRoute ( '/api/endpoint' , ( _ , res ) = > res . setHeader ( 'Content-Type' , 'application/json' ) . end ( ) ) ;
await page . goto ( ` ${ server . PREFIX } /network-tab/network.html ` ) ;
} ) ;
await traceViewer . selectAction ( 'http://localhost' ) ;
await traceViewer . showNetworkTab ( ) ;
await traceViewer . page . getByText ( 'JS' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'script.js' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByText ( 'CSS' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'style.css' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByText ( 'Image' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'image.png' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByText ( 'Fetch' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'endpoint' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByText ( 'HTML' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'network.html' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByText ( 'Font' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'font.woff2' ) ) . toBeVisible ( ) ;
} ) ;
2024-08-22 17:56:07 +02:00
test ( 'should show font preview' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( ` ${ server . PREFIX } /network-tab/network.html ` ) ;
} ) ;
await traceViewer . selectAction ( 'http://localhost' ) ;
await traceViewer . showNetworkTab ( ) ;
await traceViewer . page . getByText ( 'Font' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await traceViewer . networkRequests . getByText ( 'font.woff2' ) . click ( ) ;
await traceViewer . page . getByTestId ( 'network-request-details' ) . getByTitle ( 'Body' ) . click ( ) ;
await expect ( traceViewer . page . locator ( '.network-request-details-tab' ) ) . toContainText ( 'ABCDEF' ) ;
} ) ;
2024-08-06 23:52:35 +02:00
test ( 'should filter network requests by url' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( ` ${ server . PREFIX } /network-tab/network.html ` ) ;
} ) ;
await traceViewer . selectAction ( 'http://localhost' ) ;
await traceViewer . showNetworkTab ( ) ;
await traceViewer . page . getByPlaceholder ( 'Filter network' ) . fill ( 'script.' ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'script.js' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByPlaceholder ( 'Filter network' ) . fill ( 'png' ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'image.png' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByPlaceholder ( 'Filter network' ) . fill ( 'api/' ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'endpoint' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByPlaceholder ( 'Filter network' ) . fill ( 'End' ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'endpoint' ) ) . toBeVisible ( ) ;
await traceViewer . page . getByPlaceholder ( 'Filter network' ) . fill ( 'FON' ) ;
await expect ( traceViewer . networkRequests ) . toHaveCount ( 1 ) ;
await expect ( traceViewer . networkRequests . getByText ( 'font.woff2' ) ) . toBeVisible ( ) ;
} ) ;
2023-06-06 16:55:53 -07:00
test ( 'should have network request overrides' , async ( { page , server , runAndTrace } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . route ( '**/style.css' , route = > route . abort ( ) ) ;
await page . goto ( server . PREFIX + '/frames/frame.html' ) ;
} ) ;
await traceViewer . selectAction ( 'http://localhost' ) ;
await traceViewer . showNetworkTab ( ) ;
2024-01-10 15:28:33 -08:00
await expect ( traceViewer . networkRequests ) . toContainText ( [ /frame.htmlGET200text\/html/ ] ) ;
await expect ( traceViewer . networkRequests ) . toContainText ( [ /style.cssGETx-unknown.*aborted/ ] ) ;
2023-06-14 09:37:19 -07:00
await expect ( traceViewer . networkRequests ) . not . toContainText ( [ /continued/ ] ) ;
2023-06-06 16:55:53 -07:00
} ) ;
test ( 'should have network request overrides 2' , async ( { page , server , runAndTrace } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . route ( '**/script.js' , route = > route . continue ( ) ) ;
await page . goto ( server . PREFIX + '/frames/frame.html' ) ;
} ) ;
await traceViewer . selectAction ( 'http://localhost' ) ;
await traceViewer . showNetworkTab ( ) ;
2024-01-10 15:28:33 -08:00
await expect . soft ( traceViewer . networkRequests ) . toContainText ( [ /frame.htmlGET200text\/html.*/ ] ) ;
await expect . soft ( traceViewer . networkRequests ) . toContainText ( [ /script.jsGET200application\/javascript.*continued/ ] ) ;
2023-06-06 16:55:53 -07:00
} ) ;
2021-11-02 16:35:23 -08:00
test ( 'should show snapshot URL' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( '2+2' ) ;
} ) ;
await traceViewer . snapshotFrame ( 'page.evaluate' ) ;
2023-08-18 17:53:03 -07:00
await expect ( traceViewer . page . locator ( '.browser-frame-address-bar' ) ) . toHaveText ( server . EMPTY_PAGE ) ;
2021-11-02 16:35:23 -08:00
} ) ;
2023-01-30 19:07:52 -08:00
test ( 'should popup snapshot' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
2024-08-05 09:11:31 +02:00
await page . setContent ( 'hello äöü 🙂' ) ;
2023-01-30 19:07:52 -08:00
} ) ;
await traceViewer . snapshotFrame ( 'page.setContent' ) ;
const popupPromise = traceViewer . page . context ( ) . waitForEvent ( 'page' ) ;
await traceViewer . page . getByTitle ( 'Open snapshot in a new tab' ) . click ( ) ;
const popup = await popupPromise ;
2024-08-05 09:11:31 +02:00
await expect ( popup . getByText ( 'hello äöü 🙂' ) ) . toBeVisible ( ) ;
2023-01-30 19:07:52 -08:00
} ) ;
2021-12-09 17:10:31 -08:00
test ( 'should capture iframe with sandbox attribute' , async ( { page , server , runAndTrace } ) = > {
2021-10-12 13:42:50 -08:00
await page . route ( '**/empty.html' , route = > {
2023-06-02 21:59:12 +02:00
void route . fulfill ( {
2021-12-09 17:10:31 -08:00
body : '<iframe src="iframe.html" sandBOX="allow-scripts"></iframe>' ,
2021-10-12 13:42:50 -08:00
contentType : 'text/html'
} ) . catch ( ( ) = > { } ) ;
} ) ;
await page . route ( '**/iframe.html' , route = > {
2023-06-02 21:59:12 +02:00
void route . fulfill ( {
2021-10-12 13:42:50 -08:00
body : '<html><button>Hello iframe</button></html>' ,
contentType : 'text/html'
} ) . catch ( ( ) = > { } ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
if ( page . frames ( ) . length < 2 )
await page . waitForEvent ( 'frameattached' ) ;
await page . frames ( ) [ 1 ] . waitForSelector ( 'button' ) ;
// Force snapshot.
await page . evaluate ( '2+2' ) ;
} ) ;
// Render snapshot, check expectations.
const snapshotFrame = await traceViewer . snapshotFrame ( 'page.evaluate' , 0 , true ) ;
2023-03-21 07:40:54 -07:00
const button = snapshotFrame . frameLocator ( 'iframe' ) . locator ( 'button' ) ;
2021-10-12 13:42:50 -08:00
expect ( await button . textContent ( ) ) . toBe ( 'Hello iframe' ) ;
} ) ;
2021-12-09 17:10:31 -08:00
test ( 'should capture data-url svg iframe' , async ( { page , server , runAndTrace } ) = > {
await page . route ( '**/empty.html' , route = > {
2023-06-02 21:59:12 +02:00
void route . fulfill ( {
2021-12-09 17:10:31 -08:00
body : ` <iframe src="data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3e%3cpath d='M0 0h24v24H0z' fill='none'/%3e%3cpath d='M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z'/%3e%3c/svg%3e"></iframe> ` ,
contentType : 'text/html'
} ) . catch ( ( ) = > { } ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
if ( page . frames ( ) . length < 2 )
await page . waitForEvent ( 'frameattached' ) ;
await page . frames ( ) [ 1 ] . waitForSelector ( 'svg' ) ;
// Force snapshot.
await page . evaluate ( '2+2' ) ;
} ) ;
// Render snapshot, check expectations.
const snapshotFrame = await traceViewer . snapshotFrame ( 'page.evaluate' , 0 , true ) ;
2023-03-21 07:40:54 -07:00
await expect ( snapshotFrame . frameLocator ( 'iframe' ) . locator ( 'svg' ) ) . toBeVisible ( ) ;
const content = await snapshotFrame . frameLocator ( 'iframe' ) . locator ( ':root' ) . innerHTML ( ) ;
2021-12-09 17:10:31 -08:00
expect ( content ) . toContain ( ` d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z" ` ) ;
} ) ;
2021-10-12 13:42:50 -08:00
test ( 'should contain adopted style sheets' , async ( { page , runAndTrace , browserName } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( '<button>Hello</button>' ) ;
await page . evaluate ( ( ) = > {
const sheet = new CSSStyleSheet ( ) ;
sheet . addRule ( 'button' , 'color: red' ) ;
2024-07-03 09:40:50 +02:00
document . adoptedStyleSheets = [ sheet ] ;
2021-10-12 13:42:50 -08:00
const sheet2 = new CSSStyleSheet ( ) ;
sheet2 . addRule ( ':host' , 'color: blue' ) ;
for ( const element of [ document . createElement ( 'div' ) , document . createElement ( 'span' ) ] ) {
const root = element . attachShadow ( {
mode : 'open'
} ) ;
root . append ( 'foo' ) ;
2024-07-03 09:40:50 +02:00
root . adoptedStyleSheets = [ sheet2 ] ;
2021-10-12 13:42:50 -08:00
document . body . appendChild ( element ) ;
}
} ) ;
} ) ;
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' ) ;
2023-03-21 07:40:54 -07:00
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'color' , 'rgb(255, 0, 0)' ) ;
await expect ( frame . locator ( 'div' ) ) . toHaveCSS ( 'color' , 'rgb(0, 0, 255)' ) ;
await expect ( frame . locator ( 'span' ) ) . toHaveCSS ( 'color' , 'rgb(0, 0, 255)' ) ;
2021-10-12 13:42:50 -08:00
} ) ;
test ( 'should work with adopted style sheets and replace/replaceSync' , async ( { page , runAndTrace , browserName } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( '<button>Hello</button>' ) ;
await page . evaluate ( ( ) = > {
const sheet = new CSSStyleSheet ( ) ;
sheet . addRule ( 'button' , 'color: red' ) ;
2024-07-03 09:40:50 +02:00
document . adoptedStyleSheets = [ sheet ] ;
2021-10-12 13:42:50 -08:00
} ) ;
await page . evaluate ( ( ) = > {
2024-07-03 09:40:50 +02:00
const [ sheet ] = document . adoptedStyleSheets ;
2021-10-12 13:42:50 -08:00
sheet . replaceSync ( ` button { color: blue } ` ) ;
} ) ;
2024-07-03 09:40:50 +02:00
await page . evaluate ( async ( ) = > {
const [ sheet ] = document . adoptedStyleSheets ;
await sheet . replace ( ` button { color: #0F0 } ` ) ;
2021-10-12 13:42:50 -08:00
} ) ;
} ) ;
{
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' , 0 ) ;
2023-03-21 07:40:54 -07:00
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'color' , 'rgb(255, 0, 0)' ) ;
2021-10-12 13:42:50 -08:00
}
{
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' , 1 ) ;
2023-03-21 07:40:54 -07:00
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'color' , 'rgb(0, 0, 255)' ) ;
2021-10-12 13:42:50 -08:00
}
{
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' , 2 ) ;
2023-03-21 07:40:54 -07:00
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'color' , 'rgb(0, 255, 0)' ) ;
2021-10-12 13:42:50 -08:00
}
} ) ;
2024-07-03 09:40:50 +02:00
test ( 'should work with adopted style sheets and all: unset' , async ( { page , runAndTrace , browserName } ) = > {
test . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/31500' } ) ;
test . fixme ( browserName === 'chromium' , 'https://issues.chromium.org/u/1/issues/41416124' ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( '<button>Hello</button>' ) ;
await page . evaluate ( ( ) = > {
const stylesheet = new CSSStyleSheet ( ) ;
// 'all: unset' is the problem here.
stylesheet . replaceSync ( 'button { all: unset; border-radius: 24px; background-color: deepskyblue; color: black; padding: 5px }' ) ;
document . adoptedStyleSheets = [ stylesheet ] ;
} ) ;
await page . getByRole ( 'button' ) . click ( ) ;
} ) ;
{
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' , 0 ) ;
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'border-radius' , '24px' ) ;
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'background-color' , 'rgb(0, 191, 255)' ) ;
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'color' , 'rgb(0, 0, 0)' ) ;
await expect ( frame . locator ( 'button' ) ) . toHaveCSS ( 'padding' , '5px' ) ;
}
} ) ;
2024-07-15 17:33:22 +02:00
test ( 'should work with nesting CSS selectors' , async ( { page , runAndTrace } ) = > {
test . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/31607' } ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( `
< span class = "foo" data-testid = "green-element" > Hi < / span >
< span class = "foo bar" data-testid = "red-element" > Hello < / span >
< style >
. foo {
color : green ;
& . bar {
color : red ;
}
}
< / style >
` );
await page . evaluate ( ( ) = > { } ) ;
} ) ;
{
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' , 0 ) ;
await expect ( frame . getByTestId ( 'green-element' ) ) . toHaveCSS ( 'color' , /* green */ 'rgb(0, 128, 0)' ) ;
await expect ( frame . getByTestId ( 'red-element' ) ) . toHaveCSS ( 'color' , /* red */ 'rgb(255, 0, 0)' ) ;
}
} ) ;
2024-07-03 09:40:50 +02:00
test ( 'should restore scroll positions' , async ( { page , runAndTrace } ) = > {
2021-10-12 13:42:50 -08:00
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( `
< style >
li { height : 20px ; margin : 0 ; padding : 0 ; }
div { height : 60px ; overflow - x : hidden ; overflow - y : scroll ; background : green ; padding : 0 ; margin : 0 ; }
< / style >
< div >
< ul >
< li > Item 1 < / li >
< li > Item 2 < / li >
< li > Item 3 < / li >
< li > Item 4 < / li >
< li > Item 5 < / li >
< li > Item 6 < / li >
< li > Item 7 < / li >
< li > Item 8 < / li >
< li > Item 9 < / li >
< li > Item 10 < / li >
< / ul >
< / div >
` );
await ( await page . $ ( 'text=Item 8' ) ) . scrollIntoViewIfNeeded ( ) ;
} ) ;
// Render snapshot, check expectations.
const frame = await traceViewer . snapshotFrame ( 'scrollIntoViewIfNeeded' ) ;
2023-03-21 07:40:54 -07:00
expect ( await frame . locator ( 'div' ) . evaluate ( div = > div . scrollTop ) ) . toBe ( 136 ) ;
2021-10-12 13:42:50 -08:00
} ) ;
2023-12-01 09:38:50 -08:00
test ( 'should restore control values' , async ( { page , runAndTrace , asset } ) = > {
2022-03-21 18:51:48 -07:00
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( `
< input type = text value = old >
< input type = checkbox checked >
< input type = radio >
2023-12-01 09:38:50 -08:00
< input type = file >
2022-03-21 18:51:48 -07:00
< textarea > old < / textarea >
< select multiple >
< option value = opt1 > Hi < / option >
< option value = opt2 selected > Bye < / option >
< option value = opt3 > Hello < / option >
< / select >
< script >
document . querySelector ( '[type=text]' ) . value = 'hi' ;
document . querySelector ( '[type=checkbox]' ) . checked = false ;
document . querySelector ( '[type=radio]' ) . checked = true ;
document . querySelector ( 'textarea' ) . value = 'hello' ;
document . querySelector ( '[value=opt1]' ) . selected = true ;
document . querySelector ( '[value=opt2]' ) . selected = false ;
document . querySelector ( '[value=opt3]' ) . selected = true ;
< / script >
` );
2023-12-01 09:38:50 -08:00
await page . locator ( 'input[type="file"]' ) . setInputFiles ( asset ( 'file-to-upload.txt' ) ) ;
2022-03-21 18:51:48 -07:00
await page . click ( 'input' ) ;
} ) ;
// Render snapshot, check expectations.
const frame = await traceViewer . snapshotFrame ( 'page.click' ) ;
const text = frame . locator ( '[type=text]' ) ;
await expect ( text ) . toHaveAttribute ( 'value' , 'old' ) ;
await expect ( text ) . toHaveValue ( 'hi' ) ;
const checkbox = frame . locator ( '[type=checkbox]' ) ;
await expect ( checkbox ) . not . toBeChecked ( ) ;
expect ( await checkbox . evaluate ( c = > c . hasAttribute ( 'checked' ) ) ) . toBe ( true ) ;
const radio = frame . locator ( '[type=radio]' ) ;
await expect ( radio ) . toBeChecked ( ) ;
expect ( await radio . evaluate ( c = > c . hasAttribute ( 'checked' ) ) ) . toBe ( false ) ;
const textarea = frame . locator ( 'textarea' ) ;
await expect ( textarea ) . toHaveText ( 'old' ) ;
await expect ( textarea ) . toHaveValue ( 'hello' ) ;
2023-03-21 07:40:54 -07:00
expect ( await frame . locator ( 'option >> nth=0' ) . evaluate ( o = > o . hasAttribute ( 'selected' ) ) ) . toBe ( false ) ;
expect ( await frame . locator ( 'option >> nth=1' ) . evaluate ( o = > o . hasAttribute ( 'selected' ) ) ) . toBe ( true ) ;
expect ( await frame . locator ( 'option >> nth=2' ) . evaluate ( o = > o . hasAttribute ( 'selected' ) ) ) . toBe ( false ) ;
await expect ( frame . locator ( 'select' ) ) . toHaveValues ( [ 'opt1' , 'opt3' ] ) ;
2023-12-01 09:38:50 -08:00
await expect ( frame . locator ( 'input[type=file]' ) ) . toHaveValue ( '' ) ;
2022-03-21 18:51:48 -07:00
} ) ;
2021-10-12 13:42:50 -08:00
test ( 'should work with meta CSP' , async ( { page , runAndTrace , browserName } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( `
< head >
< meta http-equiv = "Content-Security-Policy" content = "script-src 'none'" >
< / head >
< body >
< div > Hello < / div >
< / body >
` );
await page . $eval ( 'div' , div = > {
const shadow = div . attachShadow ( { mode : 'open' } ) ;
const span = document . createElement ( 'span' ) ;
span . textContent = 'World' ;
shadow . appendChild ( span ) ;
} ) ;
} ) ;
// Render snapshot, check expectations.
const frame = await traceViewer . snapshotFrame ( '$eval' ) ;
// Should render shadow dom with post-processing script.
2023-03-21 07:40:54 -07:00
await expect ( frame . locator ( 'span' ) ) . toHaveText ( 'World' ) ;
2021-10-12 13:42:50 -08:00
} ) ;
test ( 'should handle multiple headers' , async ( { page , server , runAndTrace , browserName } ) = > {
server . setRoute ( '/foo.css' , ( req , res ) = > {
res . statusCode = 200 ;
res . setHeader ( 'vary' , [ 'accepts-encoding' , 'accepts-encoding' ] ) ;
res . end ( 'body { padding: 42px }' ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <head><link rel=stylesheet href="/foo.css"></head><body><div>Hello</div></body> ` ) ;
} ) ;
const frame = await traceViewer . snapshotFrame ( 'setContent' ) ;
2023-03-21 07:40:54 -07:00
await frame . locator ( 'div' ) . waitFor ( ) ;
await expect ( frame . locator ( 'body' ) ) . toHaveCSS ( 'padding-left' , '42px' ) ;
2021-10-12 13:42:50 -08:00
} ) ;
test ( 'should handle src=blob' , async ( { page , server , runAndTrace , browserName } ) = > {
test . skip ( browserName === 'firefox' ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setViewportSize ( { width : 300 , height : 300 } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( async ( ) = > {
const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAASCAQAAADIvofAAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfhBhAPKSstM+EuAAAAvUlEQVQY05WQIW4CYRgF599gEZgeoAKBWIfCNSmVvQMe3wv0ChhIViKwtTQEAYJwhgpISBA0JSxNIdlB7LIGTJ/8kpeZ7wW5TcT9o/QNBtvOrrWMrtg0sSGOFeELbHlCDsQ+ukeYiHNFJPHBDRKlQKVEbFkLUT3AiAxI6VGCXsWXAoQLBUl5E7HjUFwiyI4zf/wWoB3CFnxX5IeGdY8IGU/iwE9jcZrLy4pnEat+FL4hf/cbqREKo/Cf6W5zASVMeh234UtGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA2LTE2VDE1OjQxOjQzLTA3OjAwd1xNIQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNi0xNlQxNTo0MTo0My0wNzowMAYB9Z0AAAAASUVORK5CYII=' ;
const blob = await fetch ( dataUrl ) . then ( res = > res . blob ( ) ) ;
const url = window . URL . createObjectURL ( blob ) ;
const img = document . createElement ( 'img' ) ;
img . src = url ;
const loaded = new Promise ( f = > img . onload = f ) ;
document . body . appendChild ( img ) ;
await loaded ;
} ) ;
} ) ;
const frame = await traceViewer . snapshotFrame ( 'page.evaluate' ) ;
2023-03-21 07:40:54 -07:00
const size = await frame . locator ( 'img' ) . evaluate ( e = > ( e as HTMLImageElement ) . naturalWidth ) ;
2021-10-12 13:42:50 -08:00
expect ( size ) . toBe ( 10 ) ;
} ) ;
2021-10-15 15:07:15 -07:00
2024-06-10 03:44:52 -06:00
test ( 'should handle file URIs' , async ( { page , runAndTrace , browserName } ) = > {
test . skip ( browserName !== 'chromium' ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( pathToFileURL ( path . join ( __dirname , '..' , 'assets' , 'one-style.html' ) ) . href ) ;
} ) ;
const frame = await traceViewer . snapshotFrame ( 'goto' ) ;
await expect ( frame . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(255, 192, 203)' ) ;
} ) ;
2023-09-05 12:48:07 -07:00
test ( 'should preserve currentSrc' , async ( { browser , server , showTraceViewer } ) = > {
2024-09-17 15:32:30 +02:00
const traceFile = test . info ( ) . outputPath ( 'trace.zip' ) ;
2023-09-05 12:48:07 -07:00
const page = await browser . newPage ( { deviceScaleFactor : 3 } ) ;
await page . context ( ) . tracing . start ( { snapshots : true , screenshots : true , sources : true } ) ;
await page . setViewportSize ( { width : 300 , height : 300 } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( `
< picture >
< source srcset = "digits/1.png 1x, digits/2.png 2x, digits/3.png 3x" >
< img id = target1 src = "digits/0.png" >
< / picture >
< img id = target2 srcset = "digits/4.png 1x, digits/5.png 2x, digits/6.png 3x" >
` );
await page . context ( ) . tracing . stop ( { path : traceFile } ) ;
await page . close ( ) ;
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
const frame = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
await expect ( frame . locator ( '#target1' ) ) . toHaveAttribute ( 'src' , server . PREFIX + '/digits/3.png' ) ;
await expect ( frame . locator ( '#target2' ) ) . toHaveAttribute ( 'src' , server . PREFIX + '/digits/6.png' ) ;
} ) ;
2023-02-22 21:53:27 -08:00
test ( 'should register custom elements' , async ( { page , server , runAndTrace } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( ( ) = > {
customElements . define ( 'my-element' , class extends HTMLElement {
constructor ( ) {
super ( ) ;
const shadow = this . attachShadow ( { mode : 'open' } ) ;
const span = document . createElement ( 'span' ) ;
span . textContent = 'hello' ;
shadow . appendChild ( span ) ;
shadow . appendChild ( document . createElement ( 'slot' ) ) ;
}
} ) ;
} ) ;
await page . setContent ( `
< style >
: not ( : defined ) {
visibility : hidden ;
}
< / style >
< MY - element > world < / M Y - e l e m e n t >
` );
} ) ;
const frame = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
await expect ( frame . getByText ( 'worldhello' ) ) . toBeVisible ( ) ;
} ) ;
2021-10-15 15:07:15 -07:00
test ( 'should highlight target elements' , async ( { page , runAndTrace , browserName } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( `
2023-06-09 07:18:13 -07:00
< div > t1 < / div >
< div > t2 < / div >
< div > t3 < / div >
< div > t4 < / div >
< div > t5 < / div >
< div > t6 < / div >
< div > multi < / div >
< div > multi < / div >
2021-10-15 15:07:15 -07:00
` );
2023-06-09 07:18:13 -07:00
await page . click ( 'text=t1' ) ;
await page . innerText ( 'text=t2' ) ;
await ( await page . $ ( 'text=t3' ) ) . click ( ) ;
await ( await page . $ ( 'text=t4' ) ) . innerText ( ) ;
await page . locator ( 'text=t5' ) . innerText ( ) ;
await expect ( page . locator ( 'text=t6' ) ) . toHaveText ( /t6/i ) ;
await expect ( page . locator ( 'text=multi' ) ) . toHaveText ( [ 'a' , 'b' ] , { timeout : 1000 } ) . catch ( ( ) = > { } ) ;
2023-12-07 06:27:49 -08:00
await page . mouse . move ( 123 , 234 ) ;
2021-10-15 15:07:15 -07:00
} ) ;
2023-06-09 07:18:13 -07:00
async function highlightedDivs ( frameLocator : FrameLocator ) {
return frameLocator . locator ( 'div' ) . evaluateAll ( divs = > {
// See snapshotRenderer.ts for the exact color.
return divs . filter ( div = > getComputedStyle ( div ) . backgroundColor === 'rgba(111, 168, 220, 0.498)' ) . map ( div = > div . textContent ) ;
} ) ;
}
2021-10-15 15:07:15 -07:00
const framePageClick = await traceViewer . snapshotFrame ( 'page.click' ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( framePageClick ) ) . toEqual ( [ 't1' ] ) ;
2023-12-07 06:27:49 -08:00
const box1 = await framePageClick . getByText ( 't1' ) . boundingBox ( ) ;
const box2 = await framePageClick . locator ( 'x-pw-pointer' ) . boundingBox ( ) ;
const x1 = box1 ! . x + box1 ! . width / 2 ;
const y1 = box1 ! . y + box1 ! . height / 2 ;
const x2 = box2 ! . x + box2 ! . width / 2 ;
const y2 = box2 ! . y + box2 ! . height / 2 ;
expect ( Math . abs ( x1 - x2 ) < 2 ) . toBeTruthy ( ) ;
expect ( Math . abs ( y1 - y2 ) < 2 ) . toBeTruthy ( ) ;
2021-10-15 15:07:15 -07:00
const framePageInnerText = await traceViewer . snapshotFrame ( 'page.innerText' ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( framePageInnerText ) ) . toEqual ( [ 't2' ] ) ;
2021-10-15 15:07:15 -07:00
const frameHandleClick = await traceViewer . snapshotFrame ( 'elementHandle.click' ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( frameHandleClick ) ) . toEqual ( [ 't3' ] ) ;
2021-10-15 15:07:15 -07:00
const frameHandleInnerText = await traceViewer . snapshotFrame ( 'elementHandle.innerText' ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( frameHandleInnerText ) ) . toEqual ( [ 't4' ] ) ;
2021-10-15 15:07:15 -07:00
const frameLocatorInnerText = await traceViewer . snapshotFrame ( 'locator.innerText' ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( frameLocatorInnerText ) ) . toEqual ( [ 't5' ] ) ;
2021-10-15 15:07:15 -07:00
const frameExpect1 = await traceViewer . snapshotFrame ( 'expect.toHaveText' , 0 ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( frameExpect1 ) ) . toEqual ( [ 't6' ] ) ;
2021-10-15 15:07:15 -07:00
const frameExpect2 = await traceViewer . snapshotFrame ( 'expect.toHaveText' , 1 ) ;
2023-06-09 07:18:13 -07:00
await expect . poll ( ( ) = > highlightedDivs ( frameExpect2 ) ) . toEqual ( [ 'multi' , 'multi' ] ) ;
2023-12-07 06:27:49 -08:00
await expect ( frameExpect2 . locator ( 'x-pw-pointer' ) ) . not . toBeVisible ( ) ;
const frameMouseMove = await traceViewer . snapshotFrame ( 'mouse.move' ) ;
await expect ( frameMouseMove . locator ( 'x-pw-pointer' ) ) . toBeVisible ( ) ;
2021-10-15 15:07:15 -07:00
} ) ;
2021-10-23 10:23:39 -08:00
2023-08-31 12:46:49 -07:00
test ( 'should highlight target element in shadow dom' , async ( { page , server , runAndTrace } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . PREFIX + '/shadow.html' ) ;
await page . locator ( 'button' ) . click ( ) ;
await expect ( page . locator ( 'h1' ) ) . toHaveText ( 'Hellow Shadow DOM v1' ) ;
} ) ;
const framePageClick = await traceViewer . snapshotFrame ( 'locator.click' ) ;
await expect ( framePageClick . locator ( 'button' ) ) . toHaveCSS ( 'background-color' , 'rgba(111, 168, 220, 0.498)' ) ;
const frameExpect = await traceViewer . snapshotFrame ( 'expect.toHaveText' ) ;
await expect ( frameExpect . locator ( 'h1' ) ) . toHaveCSS ( 'background-color' , 'rgba(111, 168, 220, 0.498)' ) ;
} ) ;
2024-02-02 16:41:08 -08:00
test ( 'should highlight expect failure' , async ( { page , server , runAndTrace } ) = > {
test . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright-python/issues/2258' } ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
try {
await page . goto ( server . EMPTY_PAGE ) ;
await expect ( page ) . toHaveTitle ( 'foo' , { timeout : 100 } ) ;
} catch ( e ) {
}
} ) ;
await expect ( traceViewer . actionTitles . getByText ( 'expect.toHaveTitle' ) ) . toHaveCSS ( 'color' , 'rgb(176, 16, 17)' ) ;
await traceViewer . showErrorsTab ( ) ;
await expect ( traceViewer . errorMessages . nth ( 0 ) ) . toHaveText ( 'Expect failed' ) ;
} ) ;
2021-10-23 10:23:39 -08:00
test ( 'should show action source' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2022-10-18 22:23:40 -04:00
await traceViewer . selectAction ( 'locator.click' ) ;
2021-10-23 10:23:39 -08:00
const page = traceViewer . page ;
await page . click ( 'text=Source' ) ;
2022-10-18 22:23:40 -04:00
await expect ( page . locator ( '.source-line-running' ) ) . toContainText ( 'await page.getByText(\'Click\').click()' ) ;
2023-09-07 17:14:39 -07:00
await expect ( page . getByTestId ( 'stack-trace-list' ) . locator ( '.list-view-entry.selected' ) ) . toHaveText ( /doClick.*trace-viewer\.spec\.ts:[\d]+/ ) ;
2021-10-23 10:23:39 -08:00
} ) ;
2021-11-08 18:03:10 -08:00
2023-05-23 12:04:44 -07:00
test ( 'should follow redirects' , async ( { page , runAndTrace , server , asset } ) = > {
2021-11-08 18:03:10 -08:00
server . setRoute ( '/empty.html' , ( req , res ) = > {
res . writeHead ( 200 , { 'Content-Type' : 'text/html' } ) ;
res . end ( ` <div><img id=img src="image.png"></img></div> ` ) ;
} ) ;
server . setRoute ( '/image.png' , ( req , res ) = > {
res . writeHead ( 301 , { location : '/image-301.png' } ) ;
res . end ( ) ;
} ) ;
server . setRoute ( '/image-301.png' , ( req , res ) = > {
res . writeHead ( 302 , { location : '/image-302.png' } ) ;
res . end ( ) ;
} ) ;
server . setRoute ( '/image-302.png' , ( req , res ) = > {
res . writeHead ( 200 , { 'content-type' : 'image/png' } ) ;
res . end ( fs . readFileSync ( asset ( 'digits/0.png' ) ) ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
expect ( await page . evaluate ( ( ) = > ( window as any ) . img . naturalWidth ) ) . toBe ( 10 ) ;
} ) ;
const snapshotFrame = await traceViewer . snapshotFrame ( 'page.evaluate' ) ;
await expect ( snapshotFrame . locator ( 'img' ) ) . toHaveJSProperty ( 'naturalWidth' , 10 ) ;
} ) ;
2021-11-09 22:13:51 -08:00
2024-07-03 09:40:50 +02:00
test ( 'should include metainfo' , async ( { showTraceViewer } ) = > {
2022-02-07 17:05:42 -08:00
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
2021-11-09 22:13:51 -08:00
await traceViewer . page . locator ( 'text=Metadata' ) . click ( ) ;
2024-07-31 16:37:16 -07:00
const callLine = traceViewer . metadataTab . locator ( '.call-line' ) ;
2023-02-16 07:59:21 -08:00
await expect ( callLine . getByText ( 'start time' ) ) . toHaveText ( /start time:[\d/,: ]+/ ) ;
await expect ( callLine . getByText ( 'duration' ) ) . toHaveText ( /duration:[\dms]+/ ) ;
await expect ( callLine . getByText ( 'engine' ) ) . toHaveText ( /engine:[\w]+/ ) ;
await expect ( callLine . getByText ( 'platform' ) ) . toHaveText ( /platform:[\w]+/ ) ;
await expect ( callLine . getByText ( 'width' ) ) . toHaveText ( /width:[\d]+/ ) ;
await expect ( callLine . getByText ( 'height' ) ) . toHaveText ( /height:[\d]+/ ) ;
await expect ( callLine . getByText ( 'pages' ) ) . toHaveText ( /pages:1/ ) ;
await expect ( callLine . getByText ( 'actions' ) ) . toHaveText ( /actions:[\d]+/ ) ;
await expect ( callLine . getByText ( 'events' ) ) . toHaveText ( /events:[\d]+/ ) ;
2021-11-09 22:13:51 -08:00
} ) ;
2022-02-07 17:05:42 -08:00
test ( 'should open two trace files' , async ( { context , page , request , server , showTraceViewer } , testInfo ) = > {
await ( request as any ) . _tracing . start ( { snapshots : true } ) ;
2024-03-26 10:25:12 -07:00
await context . tracing . start ( { snapshots : true , sources : true } ) ;
2022-02-07 17:05:42 -08:00
{
const response = await request . get ( server . PREFIX + '/simple.json' ) ;
await expect ( response ) . toBeOK ( ) ;
}
await page . goto ( server . PREFIX + '/input/button.html' ) ;
{
const response = await request . head ( server . PREFIX + '/simplezip.json' ) ;
await expect ( response ) . toBeOK ( ) ;
}
2022-10-18 22:23:40 -04:00
await page . locator ( 'button' ) . click ( ) ;
await page . locator ( 'button' ) . click ( ) ;
2022-02-07 17:05:42 -08:00
{
const response = await request . post ( server . PREFIX + '/one-style.css' ) ;
2023-06-02 21:59:12 +02:00
await expect ( response ) . toBeOK ( ) ;
2022-02-07 17:05:42 -08:00
}
const apiTrace = testInfo . outputPath ( 'api.zip' ) ;
const contextTrace = testInfo . outputPath ( 'context.zip' ) ;
await ( request as any ) . _tracing . stop ( { path : apiTrace } ) ;
await context . tracing . stop ( { path : contextTrace } ) ;
const traceViewer = await showTraceViewer ( [ contextTrace , apiTrace ] ) ;
await traceViewer . selectAction ( 'apiRequestContext.head' ) ;
await traceViewer . selectAction ( 'apiRequestContext.get' ) ;
await traceViewer . selectAction ( 'apiRequestContext.post' ) ;
await expect ( traceViewer . actionTitles ) . toHaveText ( [
2024-07-04 17:59:56 +08:00
` apiRequestContext.get/simple.json ` ,
2022-02-07 17:05:42 -08:00
` page.gotohttp://localhost: ${ server . PORT } /input/button.html ` ,
2024-07-04 17:59:56 +08:00
` apiRequestContext.head/simplezip.json ` ,
2022-10-18 22:23:40 -04:00
` locator.clicklocator('button') ` ,
` locator.clicklocator('button') ` ,
2024-07-04 17:59:56 +08:00
` apiRequestContext.post/one-style.css ` ,
2022-02-07 17:05:42 -08:00
] ) ;
await traceViewer . page . locator ( 'text=Metadata' ) . click ( ) ;
const callLine = traceViewer . page . locator ( '.call-line' ) ;
// Should get metadata from the context trace
2023-02-16 07:59:21 -08:00
await expect ( callLine . getByText ( 'start time' ) ) . toHaveText ( /start time:[\d/,: ]+/ ) ;
2023-10-04 22:56:42 -04:00
// duration in the metadata section
2023-02-16 07:59:21 -08:00
await expect ( callLine . getByText ( 'duration' ) . first ( ) ) . toHaveText ( /duration:[\dms]+/ ) ;
await expect ( callLine . getByText ( 'engine' ) ) . toHaveText ( /engine:[\w]+/ ) ;
await expect ( callLine . getByText ( 'platform' ) ) . toHaveText ( /platform:[\w]+/ ) ;
await expect ( callLine . getByText ( 'width' ) ) . toHaveText ( /width:[\d]+/ ) ;
await expect ( callLine . getByText ( 'height' ) ) . toHaveText ( /height:[\d]+/ ) ;
await expect ( callLine . getByText ( 'pages' ) ) . toHaveText ( /pages:1/ ) ;
await expect ( callLine . getByText ( 'actions' ) ) . toHaveText ( /actions:6/ ) ;
await expect ( callLine . getByText ( 'events' ) ) . toHaveText ( /events:[\d]+/ ) ;
2022-02-07 17:05:42 -08:00
} ) ;
2024-05-08 17:33:31 -07:00
test ( 'should open two trace files of the same test' , async ( { context , page , request , server , showTraceViewer , asset } , testInfo ) = > {
const traceViewer = await showTraceViewer ( [ asset ( 'test-trace1.zip' ) , asset ( 'test-trace2.zip' ) ] ) ;
// Same actions from different test runs should not be merged.
await expect ( traceViewer . actionTitles ) . toHaveText ( [
'Before Hooks' ,
2024-05-09 15:31:23 -07:00
'Before Hooks' ,
'page.gotohttps://playwright.dev/' ,
2024-05-08 17:33:31 -07:00
'page.gotohttps://playwright.dev/' ,
'expect.toBe' ,
'After Hooks' ,
'expect.toBe' ,
'After Hooks' ,
] ) ;
} ) ;
2022-08-31 12:37:49 -07:00
test ( 'should include requestUrl in route.fulfill' , async ( { page , runAndTrace , browserName } ) = > {
await page . route ( '**/*' , route = > {
2023-06-02 21:59:12 +02:00
void route . fulfill ( {
2022-08-31 12:37:49 -07:00
status : 200 ,
headers : {
'content-type' : 'text/html'
} ,
body : 'Hello there!'
} ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( 'http://test.com' ) ;
} ) ;
// Render snapshot, check expectations.
await traceViewer . selectAction ( 'route.fulfill' ) ;
2023-02-17 11:19:53 -08:00
await traceViewer . page . locator ( '.tabbed-pane-tab-label' , { hasText : 'Call' } ) . click ( ) ;
2022-08-31 12:37:49 -07:00
const callLine = traceViewer . page . locator ( '.call-line' ) ;
2022-09-27 15:13:56 -08:00
await expect ( callLine . getByText ( 'status' ) ) . toContainText ( '200' ) ;
await expect ( callLine . getByText ( 'requestUrl' ) ) . toContainText ( 'http://test.com' ) ;
2022-08-31 12:37:49 -07:00
} ) ;
2023-03-23 01:52:04 +00:00
test ( 'should not crash with broken locator' , async ( { page , runAndTrace , server } ) = > {
test . info ( ) . annotations . push ( { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/21832' } ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
try {
await page . locator ( '[class*=github-btn] a]' ) . click ( ) ;
} catch ( e ) {
}
} ) ;
await expect ( traceViewer . page ) . toHaveTitle ( 'Playwright Trace Viewer' ) ;
const header = traceViewer . page . getByText ( 'Playwright' , { exact : true } ) ;
await expect ( header ) . toBeVisible ( ) ;
} ) ;
2022-08-31 12:37:49 -07:00
test ( 'should include requestUrl in route.continue' , async ( { page , runAndTrace , server } ) = > {
await page . route ( '**/*' , route = > {
2023-06-02 21:59:12 +02:00
void route . continue ( { url : server.EMPTY_PAGE } ) ;
2022-08-31 12:37:49 -07:00
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( 'http://test.com' ) ;
} ) ;
// Render snapshot, check expectations.
await traceViewer . selectAction ( 'route.continue' ) ;
2023-02-17 11:19:53 -08:00
await traceViewer . page . locator ( '.tabbed-pane-tab-label' , { hasText : 'Call' } ) . click ( ) ;
2022-08-31 12:37:49 -07:00
const callLine = traceViewer . page . locator ( '.call-line' ) ;
2022-09-27 15:13:56 -08:00
await expect ( callLine . getByText ( 'requestUrl' ) ) . toContainText ( 'http://test.com' ) ;
2023-02-16 07:59:21 -08:00
await expect ( callLine . getByText ( /^url:.*/ ) ) . toContainText ( server . EMPTY_PAGE ) ;
2022-08-31 12:37:49 -07:00
} ) ;
test ( 'should include requestUrl in route.abort' , async ( { page , runAndTrace , server } ) = > {
await page . route ( '**/*' , route = > {
2023-06-02 21:59:12 +02:00
void route . abort ( ) ;
2022-08-31 12:37:49 -07:00
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( 'http://test.com' ) . catch ( ( ) = > { } ) ;
} ) ;
// Render snapshot, check expectations.
await traceViewer . selectAction ( 'route.abort' ) ;
2023-02-17 11:19:53 -08:00
await traceViewer . page . locator ( '.tabbed-pane-tab-label' , { hasText : 'Call' } ) . click ( ) ;
2022-08-31 12:37:49 -07:00
const callLine = traceViewer . page . locator ( '.call-line' ) ;
2022-09-27 15:13:56 -08:00
await expect ( callLine . getByText ( 'requestUrl' ) ) . toContainText ( 'http://test.com' ) ;
2022-08-31 12:37:49 -07:00
} ) ;
2022-09-04 10:52:20 -07:00
test ( 'should serve overridden request' , async ( { page , runAndTrace , server } ) = > {
server . setRoute ( '/custom.css' , ( req , res ) = > {
res . writeHead ( 200 , {
'Content-Type' : 'text/css' ,
} ) ;
res . end ( ` body { background: red } ` ) ;
} ) ;
await page . route ( '**/one-style.css' , route = > {
2023-06-02 21:59:12 +02:00
void route . continue ( {
2022-09-04 10:52:20 -07:00
url : server.PREFIX + '/custom.css'
} ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . PREFIX + '/one-style.html' ) ;
} ) ;
// Render snapshot, check expectations.
const snapshotFrame = await traceViewer . snapshotFrame ( 'page.goto' ) ;
2023-03-21 07:40:54 -07:00
await expect ( snapshotFrame . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(255, 0, 0)' ) ;
2022-09-04 10:52:20 -07:00
} ) ;
2022-10-07 11:27:56 -07:00
test ( 'should display waitForLoadState even if did not wait for it' , async ( { runAndTrace , server , page } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . waitForLoadState ( 'load' ) ;
await page . waitForLoadState ( 'load' ) ;
} ) ;
await expect ( traceViewer . actionTitles ) . toHaveText ( [
/page.goto/ ,
/page.waitForLoadState/ ,
/page.waitForLoadState/ ,
] ) ;
} ) ;
2022-10-18 22:23:40 -04:00
test ( 'should display language-specific locators' , async ( { runAndTrace , server , page , toImpl } ) = > {
2023-06-01 17:54:43 -07:00
toImpl ( page ) . attribution . playwright . options . sdkLanguage = 'python' ;
2022-10-18 22:23:40 -04:00
const traceViewer = await runAndTrace ( async ( ) = > {
await page . setContent ( '<button>Submit</button>' ) ;
await page . getByRole ( 'button' , { name : 'Submit' } ) . click ( ) ;
} ) ;
await expect ( traceViewer . actionTitles ) . toHaveText ( [
/page.setContent/ ,
/locator.clickget_by_role\("button", name="Submit"\)/ ,
] ) ;
2023-06-01 17:54:43 -07:00
toImpl ( page ) . attribution . playwright . options . sdkLanguage = 'javascript' ;
2022-10-18 22:23:40 -04:00
} ) ;
2023-02-17 11:19:53 -08:00
test ( 'should pick locator' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( '<button>Submit</button>' ) ;
} ) ;
const snapshot = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
await traceViewer . page . getByTitle ( 'Pick locator' ) . click ( ) ;
2023-03-21 07:40:54 -07:00
await snapshot . locator ( 'button' ) . click ( ) ;
2023-02-17 11:19:53 -08:00
await expect ( traceViewer . page . locator ( '.cm-wrapper' ) ) . toContainText ( ` getByRole('button', { name: 'Submit' }) ` ) ;
} ) ;
test ( 'should update highlight when typing' , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( '<button>Submit</button>' ) ;
} ) ;
const snapshot = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
2023-08-18 17:53:03 -07:00
await traceViewer . page . getByText ( 'Locator' ) . click ( ) ;
2023-02-17 11:19:53 -08:00
await traceViewer . page . locator ( '.CodeMirror' ) . click ( ) ;
await traceViewer . page . keyboard . type ( 'button' ) ;
await expect ( snapshot . locator ( 'x-pw-glass' ) ) . toBeVisible ( ) ;
} ) ;
2023-03-23 12:49:53 -07:00
test ( 'should open trace-1.31' , async ( { showTraceViewer } ) = > {
const traceViewer = await showTraceViewer ( [ path . join ( __dirname , '../assets/trace-1.31.zip' ) ] ) ;
const snapshot = await traceViewer . snapshotFrame ( 'locator.click' ) ;
await expect ( snapshot . locator ( '[__playwright_target__]' ) ) . toHaveText ( [ 'Submit' ] ) ;
} ) ;
2023-06-17 06:58:16 -07:00
2023-09-19 16:21:09 -07:00
test ( 'should open trace-1.37' , async ( { showTraceViewer } ) = > {
const traceViewer = await showTraceViewer ( [ path . join ( __dirname , '../assets/trace-1.37.zip' ) ] ) ;
const snapshot = await traceViewer . snapshotFrame ( 'page.goto' ) ;
await expect ( snapshot . locator ( 'div' ) ) . toHaveCSS ( 'background-color' , 'rgb(255, 0, 0)' ) ;
await traceViewer . showConsoleTab ( ) ;
await expect ( traceViewer . consoleLineMessages ) . toHaveText ( [ 'hello {foo: bar}' ] ) ;
await traceViewer . showNetworkTab ( ) ;
2024-01-10 15:28:33 -08:00
await expect ( traceViewer . networkRequests ) . toContainText ( [
/index.htmlGET200text\/html/ ,
/style.cssGET200x-unknown/
] ) ;
2023-09-19 16:21:09 -07:00
} ) ;
2023-07-10 20:04:48 -07:00
test ( 'should prefer later resource request with the same method' , async ( { page , server , runAndTrace } ) = > {
2023-06-17 06:58:16 -07:00
const html = `
< body >
< script >
const link = document . createElement ( 'link' ) ;
link . rel = 'stylesheet' ;
link . href = 'style.css' ;
document . head . appendChild ( link ) ;
if ( ! window . location . href . includes ( 'reloaded' ) )
window . location . href = window . location . href + '?reloaded' ;
2023-07-10 20:04:48 -07:00
else
link . onload = ( ) = > fetch ( 'style.css' , { method : 'HEAD' } ) ;
2023-06-17 06:58:16 -07:00
< / script >
2023-07-10 20:04:48 -07:00
< div > Hello < / div >
2023-06-17 06:58:16 -07:00
< / body >
` ;
let reloadStartedCallback = ( ) = > { } ;
const reloadStartedPromise = new Promise < void > ( f = > reloadStartedCallback = f ) ;
server . setRoute ( '/style.css' , async ( req , res ) = > {
2023-07-10 20:04:48 -07:00
if ( req . method === 'HEAD' ) {
res . statusCode = 200 ;
res . end ( '' ) ;
return ;
}
2023-06-17 06:58:16 -07:00
// Make sure reload happens before style arrives.
await reloadStartedPromise ;
res . end ( 'body { background-color: rgb(123, 123, 123) }' ) ;
} ) ;
server . setRoute ( '/index.html' , ( req , res ) = > res . end ( html ) ) ;
server . setRoute ( '/index.html?reloaded' , ( req , res ) = > {
reloadStartedCallback ( ) ;
res . end ( html ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
2023-07-10 20:04:48 -07:00
const headRequest = page . waitForRequest ( req = > req . url ( ) === server . PREFIX + '/style.css' && req . method ( ) === 'HEAD' ) ;
2023-06-17 06:58:16 -07:00
await page . goto ( server . PREFIX + '/index.html' ) ;
2023-07-10 20:04:48 -07:00
await headRequest ;
await page . locator ( 'div' ) . click ( ) ;
2023-06-17 06:58:16 -07:00
} ) ;
2023-07-10 20:04:48 -07:00
const frame1 = await traceViewer . snapshotFrame ( 'page.goto' ) ;
await expect ( frame1 . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(123, 123, 123)' ) ;
const frame2 = await traceViewer . snapshotFrame ( 'locator.click' ) ;
await expect ( frame2 . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(123, 123, 123)' ) ;
2023-06-17 06:58:16 -07:00
} ) ;
2023-07-27 08:06:00 -07:00
test ( 'should ignore 304 responses' , async ( { page , server , runAndTrace } ) = > {
const html = `
< head >
< link rel = stylesheet href = "style.css" / >
< / head >
< body >
< div > Hello < / div >
< / body >
` ;
server . setRoute ( '/style.css' , async ( req , res ) = > {
if ( req . headers [ 'if-modified-since' ] ) {
res . statusCode = 304 ; // not modified
res . end ( ) ;
return ;
}
res . setHeader ( 'Cache-Control' , 'public, max-age=31536000, no-cache' ) ;
res . setHeader ( 'Last-Modified' , ( new Date ( ) ) . toISOString ( ) ) ;
res . end ( 'body { background-color: rgb(123, 123, 123) }' ) ;
} ) ;
server . setRoute ( '/index.html' , ( req , res ) = > res . end ( html ) ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
const request1 = page . waitForEvent ( 'requestfinished' , req = > req . url ( ) === server . PREFIX + '/style.css' ) ;
await page . goto ( server . PREFIX + '/index.html' ) ;
await request1 ;
await page . waitForTimeout ( 1000 ) ;
const request2 = page . waitForEvent ( 'requestfinished' , req = > req . url ( ) === server . PREFIX + '/style.css' ) ;
await page . goto ( server . PREFIX + '/index.html' ) ;
await request2 ;
await page . waitForTimeout ( 1000 ) ;
await page . locator ( 'div' ) . click ( ) ;
} ) ;
const frame = await traceViewer . snapshotFrame ( 'locator.click' ) ;
await expect ( frame . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(123, 123, 123)' ) ;
} ) ;
2023-09-06 09:44:47 -07:00
test ( 'should pick locator in iframe' , async ( { page , runAndTrace , server } ) = > {
/ *
iframe [ id = frame1 ]
div Hello1
iframe
div Hello2
iframe [ name = one ]
div HelloNameOne
iframe [ name = two ]
dev HelloNameTwo
* /
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <iframe id=frame1 srcdoc="<div>Hello1</div><iframe srcdoc='<div>Hello2</div><iframe name=one></iframe><iframe name=two></iframe><iframe></iframe>'>"> ` ) ;
const frameOne = page . frame ( { name : 'one' } ) ;
await frameOne . setContent ( ` <div>HelloNameOne</div> ` ) ;
const frameTwo = page . frame ( { name : 'two' } ) ;
await frameTwo . setContent ( ` <div>HelloNameTwo</div> ` ) ;
await page . evaluate ( '2+2' ) ;
} ) ;
await traceViewer . page . getByTitle ( 'Pick locator' ) . click ( ) ;
const cmWrapper = traceViewer . page . locator ( '.cm-wrapper' ) ;
const snapshot = await traceViewer . snapshotFrame ( 'page.evaluate' ) ;
await snapshot . frameLocator ( '#frame1' ) . getByText ( 'Hello1' ) . click ( ) ;
2024-09-18 20:15:01 -07:00
await expect . soft ( cmWrapper ) . toContainText ( ` locator('#frame1').contentFrame().getByText('Hello1') ` ) ;
2023-09-06 09:44:47 -07:00
await snapshot . frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . getByText ( 'Hello2' ) . click ( ) ;
2024-09-18 20:15:01 -07:00
await expect . soft ( cmWrapper ) . toContainText ( ` locator('#frame1').contentFrame().locator('iframe').contentFrame().getByText('Hello2') ` , { timeout : 0 } ) ;
2023-09-06 09:44:47 -07:00
await snapshot . frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . frameLocator ( '[name=one]' ) . getByText ( 'HelloNameOne' ) . click ( ) ;
2024-09-18 20:15:01 -07:00
await expect . soft ( cmWrapper ) . toContainText ( ` locator('#frame1').contentFrame().locator('iframe').contentFrame().locator('iframe[name="one"]').contentFrame().getByText('HelloNameOne') ` , { timeout : 0 } ) ;
2023-09-06 09:44:47 -07:00
await snapshot . frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . frameLocator ( '[name=two]' ) . getByText ( 'HelloNameTwo' ) . click ( ) ;
2024-09-18 20:15:01 -07:00
await expect . soft ( cmWrapper ) . toContainText ( ` locator('#frame1').contentFrame().locator('iframe').contentFrame().locator('iframe[name="two"]').contentFrame().getByText('HelloNameTwo') ` , { timeout : 0 } ) ;
2023-09-06 09:44:47 -07:00
} ) ;
2023-09-06 16:14:40 -07:00
test ( 'should highlight locator in iframe while typing' , async ( { page , runAndTrace , server , platform } ) = > {
/ *
iframe [ id = frame1 ]
div Hello1
iframe
div Hello2
iframe [ name = one ]
div HelloNameOne
iframe [ name = two ]
dev HelloNameTwo
* /
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( ` <iframe id=frame1 srcdoc="<div>Hello1</div><iframe srcdoc='<div>Hello2</div><iframe name=one></iframe><iframe name=two></iframe><iframe></iframe>'>"> ` ) ;
const frameOne = page . frame ( { name : 'one' } ) ;
await frameOne . setContent ( ` <div>HelloNameOne</div> ` ) ;
const frameTwo = page . frame ( { name : 'two' } ) ;
await frameTwo . setContent ( ` <div>HelloNameTwo</div> ` ) ;
await page . evaluate ( '2+2' ) ;
} ) ;
const snapshot = await traceViewer . snapshotFrame ( 'page.evaluate' ) ;
await traceViewer . page . getByText ( 'Locator' ) . click ( ) ;
await traceViewer . page . locator ( '.CodeMirror' ) . click ( ) ;
const locators = [ {
2024-09-18 20:15:01 -07:00
text : ` locator('#frame1').contentFrame().getByText('Hello1') ` ,
2023-09-06 16:14:40 -07:00
element : snapshot.frameLocator ( '#frame1' ) . locator ( 'div' , { hasText : 'Hello1' } ) ,
highlight : snapshot.frameLocator ( '#frame1' ) . locator ( 'x-pw-highlight' ) ,
} , {
2024-09-18 20:15:01 -07:00
text : ` locator('#frame1').contentFrame().locator('iframe').contentFrame().getByText('Hello2') ` ,
2023-09-06 16:14:40 -07:00
element : snapshot.frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . locator ( 'div' , { hasText : 'Hello2' } ) ,
highlight : snapshot.frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . locator ( 'x-pw-highlight' ) ,
} , {
2024-09-18 20:15:01 -07:00
text : ` locator('#frame1').contentFrame().locator('iframe').contentFrame().locator('iframe[name="one"]').contentFrame().getByText('HelloNameOne') ` ,
2023-09-06 16:14:40 -07:00
element : snapshot.frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . frameLocator ( 'iframe[name="one"]' ) . locator ( 'div' , { hasText : 'HelloNameOne' } ) ,
highlight : snapshot.frameLocator ( '#frame1' ) . frameLocator ( 'iframe' ) . frameLocator ( 'iframe[name="one"]' ) . locator ( 'x-pw-highlight' ) ,
} ] ;
for ( const locator of locators ) {
if ( platform === 'darwin' )
await traceViewer . page . keyboard . press ( 'Meta+a' ) ;
else
await traceViewer . page . keyboard . press ( 'Control+a' ) ;
await traceViewer . page . keyboard . press ( 'Backspace' ) ;
await traceViewer . page . keyboard . type ( locator . text ) ;
const elementBox = await locator . element . boundingBox ( ) ;
const highlightBox = await locator . highlight . boundingBox ( ) ;
expect ( Math . abs ( elementBox . width - highlightBox . width ) ) . toBeLessThan ( 5 ) ;
expect ( Math . abs ( elementBox . height - highlightBox . height ) ) . toBeLessThan ( 5 ) ;
expect ( Math . abs ( elementBox . x - highlightBox . x ) ) . toBeLessThan ( 5 ) ;
expect ( Math . abs ( elementBox . y - highlightBox . y ) ) . toBeLessThan ( 5 ) ;
}
} ) ;
2024-01-12 12:11:39 -08:00
test ( 'should preserve noscript when javascript is disabled' , async ( { browser , server , showTraceViewer } ) = > {
2024-09-17 15:32:30 +02:00
const traceFile = test . info ( ) . outputPath ( 'trace.zip' ) ;
2024-01-12 12:11:39 -08:00
const page = await browser . newPage ( { javaScriptEnabled : false } ) ;
await page . context ( ) . tracing . start ( { snapshots : true , screenshots : true , sources : true } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( `
< body >
< noscript > javascript is disabled ! < / noscript >
< / body >
` );
await page . context ( ) . tracing . stop ( { path : traceFile } ) ;
await page . close ( ) ;
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
const frame = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
await expect ( frame . getByText ( 'javascript is disabled!' ) ) . toBeVisible ( ) ;
} ) ;
2024-04-12 20:26:52 +02:00
2024-09-17 15:32:30 +02:00
test ( 'should remove noscript by default' , async ( { browser , server , showTraceViewer , browserType } ) = > {
const traceFile = test . info ( ) . outputPath ( 'trace.zip' ) ;
2024-04-12 20:26:52 +02:00
const page = await browser . newPage ( { javaScriptEnabled : undefined } ) ;
await page . context ( ) . tracing . start ( { snapshots : true , screenshots : true , sources : true } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( `
< noscript > Enable JavaScript to run this app . < / noscript >
< div > Always visible < / div >
` );
await page . context ( ) . tracing . stop ( { path : traceFile } ) ;
await page . close ( ) ;
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
const frame = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
await expect ( frame . getByText ( 'Always visible' ) ) . toBeVisible ( ) ;
await expect ( frame . getByText ( 'Enable JavaScript to run this app.' ) ) . toBeHidden ( ) ;
} ) ;
2024-09-17 15:32:30 +02:00
test ( 'should remove noscript when javaScriptEnabled is set to true' , async ( { browser , server , showTraceViewer , browserType } ) = > {
const traceFile = test . info ( ) . outputPath ( 'trace.zip' ) ;
2024-04-12 20:26:52 +02:00
const page = await browser . newPage ( { javaScriptEnabled : true } ) ;
await page . context ( ) . tracing . start ( { snapshots : true , screenshots : true , sources : true } ) ;
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( `
< noscript > Enable JavaScript to run this app . < / noscript >
< div > Always visible < / div >
` );
await page . context ( ) . tracing . stop ( { path : traceFile } ) ;
await page . close ( ) ;
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
const frame = await traceViewer . snapshotFrame ( 'page.setContent' ) ;
await expect ( frame . getByText ( 'Always visible' ) ) . toBeVisible ( ) ;
await expect ( frame . getByText ( 'Enable JavaScript to run this app.' ) ) . toBeHidden ( ) ;
} ) ;
2024-05-28 20:14:22 +00:00
test ( 'should open snapshot in new browser context' , async ( { browser , page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . setContent ( 'hello' ) ;
} ) ;
await traceViewer . snapshotFrame ( 'page.setContent' ) ;
const popupPromise = traceViewer . page . context ( ) . waitForEvent ( 'page' ) ;
await traceViewer . page . getByTitle ( 'Open snapshot in a new tab' ) . click ( ) ;
const popup = await popupPromise ;
// doesn't share sw.bundle.js
const newPage = await browser . newPage ( ) ;
await newPage . goto ( popup . url ( ) ) ;
await expect ( newPage . getByText ( 'hello' ) ) . toBeVisible ( ) ;
await newPage . close ( ) ;
} ) ;
2024-06-19 15:06:20 -07:00
2024-07-19 11:18:22 -07:00
test ( 'should show similar actions from library-only trace' , async ( { showTraceViewer , asset } ) = > {
const traceViewer = await showTraceViewer ( [ asset ( 'trace-library-1.46.zip' ) ] ) ;
await expect ( traceViewer . actionTitles ) . toHaveText ( [
/page.setContent/ ,
/locator.getAttributelocator\('div'\)/ ,
/locator.isVisiblelocator\('div'\)/ ,
/locator.getAttributelocator\('div'\)/ ,
/locator.isVisiblelocator\('div'\)/ ,
] ) ;
} ) ;
2024-06-19 15:06:20 -07:00
function parseMillis ( s : string ) : number {
const matchMs = s . match ( /(\d+)ms/ ) ;
if ( matchMs )
return + matchMs [ 1 ] ;
const matchSeconds = s . match ( /([\d.]+)s/ ) ;
if ( ! matchSeconds )
throw new Error ( 'Failed to parse to millis: ' + s ) ;
return ( + matchSeconds [ 1 ] ) * 1000 ;
}
test ( 'should show correct request start time' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/31133' } ,
} , async ( { page , runAndTrace , server } ) = > {
server . setRoute ( '/api' , ( req , res ) = > {
setTimeout ( ( ) = > {
res . writeHead ( 200 , { 'Content-Type' : 'text/plain' } ) ;
res . end ( 'done' ) ;
} , 1100 ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . EMPTY_PAGE ) ;
await page . evaluate ( ( ) = > {
return fetch ( '/api' ) . then ( r = > r . text ( ) ) ;
} ) ;
} ) ;
await traceViewer . selectAction ( 'page.evaluate' ) ;
await traceViewer . showNetworkTab ( ) ;
await expect ( traceViewer . networkRequests ) . toContainText ( [ /apiGET200text/ ] ) ;
const line = traceViewer . networkRequests . getByText ( /apiGET200text/ ) ;
const start = await line . locator ( '.grid-view-column-start' ) . textContent ( ) ;
const duration = await line . locator ( '.grid-view-column-duration' ) . textContent ( ) ;
expect ( parseMillis ( duration ) ) . toBeGreaterThan ( 1000 ) ;
expect ( parseMillis ( start ) ) . toBeLessThan ( 1000 ) ;
} ) ;
2024-07-22 11:34:34 -07:00
test ( 'should allow hiding route actions' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/30970' } ,
} , async ( { page , runAndTrace , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . route ( '**/*' , async route = > {
await route . fulfill ( { contentType : 'text/html' , body : 'Yo, page!' } ) ;
} ) ;
await page . goto ( server . EMPTY_PAGE ) ;
} ) ;
// Routes are visible by default.
await expect ( traceViewer . actionTitles ) . toHaveText ( [
/page.route/ ,
/page.goto.*empty.html/ ,
/route.fulfill/ ,
] ) ;
await traceViewer . page . getByText ( 'Settings' ) . click ( ) ;
await expect ( traceViewer . page . getByRole ( 'checkbox' , { name : 'Show route actions' } ) ) . toBeChecked ( ) ;
await traceViewer . page . getByRole ( 'checkbox' , { name : 'Show route actions' } ) . uncheck ( ) ;
await traceViewer . page . getByText ( 'Actions' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . actionTitles ) . toHaveText ( [
/page.goto.*empty.html/ ,
] ) ;
await traceViewer . page . getByText ( 'Settings' ) . click ( ) ;
await traceViewer . page . getByRole ( 'checkbox' , { name : 'Show route actions' } ) . check ( ) ;
await traceViewer . page . getByText ( 'Actions' , { exact : true } ) . click ( ) ;
await expect ( traceViewer . actionTitles ) . toHaveText ( [
/page.route/ ,
/page.goto.*empty.html/ ,
/route.fulfill/ ,
] ) ;
} ) ;
2024-07-25 17:14:46 +02:00
test ( 'should show baseURL in metadata pane' , {
annotation : { type : 'issue' , description : 'https://github.com/microsoft/playwright/issues/31847' } ,
} , async ( { showTraceViewer } ) = > {
const traceViewer = await showTraceViewer ( [ traceFile ] ) ;
await traceViewer . selectAction ( 'page.evaluate' ) ;
await traceViewer . showMetadataTab ( ) ;
await expect ( traceViewer . metadataTab ) . toContainText ( 'baseURL:https://example.com' ) ;
} ) ;
2024-08-19 10:29:51 -07:00
test ( 'should serve css without content-type' , async ( { page , runAndTrace , server } ) = > {
server . setRoute ( '/one-style.css' , ( req , res ) = > {
res . writeHead ( 200 ) ;
res . end ( ` body { background: red } ` ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . PREFIX + '/one-style.html' ) ;
} ) ;
const snapshotFrame = await traceViewer . snapshotFrame ( 'page.goto' ) ;
await expect ( snapshotFrame . locator ( 'body' ) ) . toHaveCSS ( 'background-color' , 'rgb(255, 0, 0)' , { timeout : 0 } ) ;
} ) ;
2024-09-06 16:24:33 +02:00
test ( 'should allow showing screenshots instead of snapshots' , async ( { runAndTrace , page , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . PREFIX + '/one-style.html' ) ;
2024-09-09 14:00:38 +02:00
await page . waitForTimeout ( 1000 ) ; // ensure we could take a screenshot
2024-09-06 16:24:33 +02:00
} ) ;
const screenshot = traceViewer . page . getByAltText ( ` Screenshot of page.goto > Action ` ) ;
const snapshot = ( await traceViewer . snapshotFrame ( 'page.goto' ) ) . owner ( ) ;
await expect ( snapshot ) . toBeVisible ( ) ;
await expect ( screenshot ) . not . toBeVisible ( ) ;
await traceViewer . page . getByTitle ( 'Settings' ) . click ( ) ;
await traceViewer . page . getByText ( 'Show screenshot instead of snapshot' ) . setChecked ( true ) ;
await expect ( snapshot ) . not . toBeVisible ( ) ;
await expect ( screenshot ) . toBeVisible ( ) ;
} ) ;
test ( 'should handle case where neither snapshots nor screenshots exist' , async ( { runAndTrace , page , server } ) = > {
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . PREFIX + '/one-style.html' ) ;
} , { snapshots : false , screenshots : false } ) ;
await traceViewer . page . getByTitle ( 'Settings' ) . click ( ) ;
await traceViewer . page . getByText ( 'Show screenshot instead of snapshot' ) . setChecked ( true ) ;
const screenshot = traceViewer . page . getByAltText ( ` Screenshot of page.goto > Action ` ) ;
await expect ( screenshot ) . not . toBeVisible ( ) ;
} ) ;
2024-09-11 03:04:03 -07:00
test ( 'should show only one pointer with multilevel iframes' , async ( { page , runAndTrace , server , browserName } ) = > {
test . fixme ( browserName !== 'chromium' , 'Elements in iframe are not marked' ) ;
server . setRoute ( '/level-0.html' , ( req , res ) = > {
res . writeHead ( 200 ) ;
res . end ( ` <iframe src="/level-1.html" style="position: absolute; left: 100px"></iframe> ` ) ;
} ) ;
server . setRoute ( '/level-1.html' , ( req , res ) = > {
res . writeHead ( 200 ) ;
res . end ( ` <iframe src="/level-2.html"></iframe> ` ) ;
} ) ;
server . setRoute ( '/level-2.html' , ( req , res ) = > {
res . writeHead ( 200 ) ;
res . end ( ` <button>Click me</button> ` ) ;
} ) ;
const traceViewer = await runAndTrace ( async ( ) = > {
await page . goto ( server . PREFIX + '/level-0.html' ) ;
await page . frameLocator ( 'iframe' ) . frameLocator ( 'iframe' ) . locator ( 'button' ) . click ( { position : { x : 5 , y : 5 } } ) ;
} ) ;
const snapshotFrame = await traceViewer . snapshotFrame ( 'locator.click' ) ;
await expect . soft ( snapshotFrame . locator ( 'x-pw-pointer' ) ) . not . toBeAttached ( ) ;
await expect . soft ( snapshotFrame . frameLocator ( 'iframe' ) . locator ( 'x-pw-pointer' ) ) . not . toBeAttached ( ) ;
await expect . soft ( snapshotFrame . frameLocator ( 'iframe' ) . frameLocator ( 'iframe' ) . locator ( 'x-pw-pointer' ) ) . toBeVisible ( ) ;
} ) ;