-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat(playwright-html-report): added global checkbox for enabling/disabling html snippets to Playwright Report #35203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
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. | ||
*/ | ||
.setting { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please format the source consistently with other files, keeping the same indentation? |
||
line-height: 38px; | ||
padding-left: 9px; | ||
} | ||
|
||
.default { | ||
margin-right: 7px; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment here about the copyright being off. |
||
Copyright (c) Microsoft Corporation. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import * as React from 'react'; | ||
import './checkbox.css'; | ||
|
||
export type Check<T> = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment here, please format consistently. |
||
value: T; | ||
set: (value: T) => void; | ||
name: string; | ||
title?: string; | ||
}; | ||
|
||
export const CheckBox: React.FunctionComponent<{ | ||
checkBoxSettings: Check<boolean>[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are there multiple "settings" passed to a single checkbox? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thought process was to follow the same format as another component utilizing a checkbox style component (DefaultSettingView.tsx) However, looking at it again I can see what you mean, I plan on removing the 'name' and 'title' attributes from the required prop. |
||
}> = ({ checkBoxSettings }) => { | ||
return ( | ||
<> | ||
{checkBoxSettings.map(({ value, set, name, title }) => { | ||
const labelId = `checkBoxSetting-${name}`; | ||
|
||
return ( | ||
<div key={name} className='setting' title={title}> | ||
<input | ||
className = 'default' | ||
type='checkbox' | ||
id={labelId} | ||
checked={value} | ||
onChange={() => set(!value)} | ||
/> | ||
<label htmlFor={labelId}>{name}</label> | ||
</div> | ||
); | ||
})} | ||
</> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import { ImageDiffView } from '@web/shared/imageDiffView'; | |
import { CodeSnippet, TestErrorView, TestScreenshotErrorView } from './testErrorView'; | ||
import * as icons from './icons'; | ||
import './testResultView.css'; | ||
import { CheckBox } from './checkbox'; | ||
|
||
interface ImageDiffWithAnchors extends ImageDiff { | ||
anchors: string[]; | ||
|
@@ -71,6 +72,7 @@ export const TestResultView: React.FC<{ | |
test: TestCase, | ||
result: TestResult, | ||
}> = ({ test, result }) => { | ||
const [showSnippets, setShowSnippets] = React.useState(true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be a global setting applying to all test results, so it must live somewhere higher up in the components tree. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const { screenshots, videos, traces, otherAttachments, diffs, errors, otherAttachmentAnchors, screenshotAnchors } = React.useMemo(() => { | ||
const attachments = result.attachments.filter(a => !a.name.startsWith('_')); | ||
const screenshots = new Set(attachments.filter(a => a.contentType.startsWith('image/'))); | ||
|
@@ -94,7 +96,8 @@ export const TestResultView: React.FC<{ | |
})} | ||
</AutoChip>} | ||
{!!result.steps.length && <AutoChip header='Test Steps'> | ||
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} result={result} test={test} depth={0}/>)} | ||
<CheckBox checkBoxSettings={[{ value: showSnippets, set: setShowSnippets, name: 'Show Snippets' }]} /> | ||
{result.steps.map((step, i) => <StepTreeItem key={`step-${i}`} step={step} result={result} test={test} depth={0} showSnippets={showSnippets}/>)} | ||
</AutoChip>} | ||
|
||
{diffs.map((diff, index) => | ||
|
@@ -175,17 +178,18 @@ const StepTreeItem: React.FC<{ | |
result: TestResult; | ||
step: TestStep; | ||
depth: number, | ||
}> = ({ test, step, result, depth }) => { | ||
showSnippets: boolean, | ||
}> = ({ test, step, result, depth, showSnippets }) => { | ||
return <TreeItem title={<span aria-label={step.title}> | ||
<span style={{ float: 'right' }}>{msToString(step.duration)}</span> | ||
{step.attachments.length > 0 && <a style={{ float: 'right' }} title={`reveal attachment`} href={testResultHref({ test, result, anchor: `attachment-${step.attachments[0]}` })} onClick={evt => { evt.stopPropagation(); }}>{icons.attachment()}</a>} | ||
{statusIcon(step.error || step.duration === -1 ? 'failed' : (step.skipped ? 'skipped' : 'passed'))} | ||
<span>{step.title}</span> | ||
{step.count > 1 && <> ✕ <span className='test-result-counter'>{step.count}</span></>} | ||
{step.location && <span className='test-result-path'>— {step.location.file}:{step.location.line}</span>} | ||
</span>} loadChildren={step.steps.length || step.snippet ? () => { | ||
const snippet = step.snippet ? [<CodeSnippet testId='test-snippet' key='line' code={step.snippet} />] : []; | ||
const steps = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} />); | ||
</span>} loadChildren={step.steps.length || (step.snippet && showSnippets) ? () => { | ||
const snippet = (step.snippet && showSnippets) ? [<CodeSnippet testId='test-snippet' key='line' code={step.snippet} />] : []; | ||
const steps = step.steps.map((s, i) => <StepTreeItem key={i} step={s} depth={depth + 1} result={result} test={test} showSnippets={showSnippets}/>); | ||
return snippet.concat(steps); | ||
} : undefined} depth={depth}/>; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2790,6 +2790,69 @@ for (const useIntermediateMergeReport of [true, false] as const) { | |
await page.getByRole('link', { name: 'sample' }).click(); | ||
await expect(page.getByRole('button', { name: 'Copy prompt' })).toHaveCount(1); | ||
}); | ||
|
||
test('should toggle code snippet visibility', async ({ runInlineTest, showReport, page }) => { | ||
await runInlineTest({ | ||
'a.test.js': ` | ||
const { test, expect } = require('@playwright/test'); | ||
|
||
test('test with multiple steps and snippets', async ({ page }) => { | ||
console.log('Test started'); // First snippet | ||
|
||
await test.step('Step 1: Navigate to example page', async () => { | ||
await page.goto('https://example.com'); | ||
console.log('Navigated to example.com'); // Second snippet | ||
}); | ||
|
||
await test.step('Step 2: Check page title', async () => { | ||
const title = await page.title(); | ||
console.log('Page title is:', title); // Third snippet | ||
expect(title).toBe('Example Domain'); // Fourth snippet | ||
}); | ||
|
||
await test.step('Step 3: Find and click a link', async () => { | ||
const link = page.locator('a'); | ||
await expect(link).toBeVisible(); | ||
console.log('Link is visible'); // Fifth snippet | ||
await link.click(); | ||
console.log('Clicked the link'); // Sixth snippet | ||
}); | ||
|
||
console.log('Test finished'); // Seventh snippet | ||
}); | ||
`, | ||
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }); | ||
|
||
|
||
await showReport(); | ||
|
||
const testLink = page.locator('a[title*="test with multiple steps and snippets"]').nth(0); | ||
await expect(testLink).toBeVisible(); | ||
await testLink.click(); | ||
|
||
await expect(page).toHaveURL(/#\?testId=/); | ||
const toggleCheckbox = page.locator('#checkBoxSetting-Show\\ Snippets'); | ||
await expect(toggleCheckbox).toBeVisible(); | ||
|
||
const secondTreeItem = page.locator('.tree-item-title').nth(1); | ||
await expect(secondTreeItem).toBeVisible(); | ||
await secondTreeItem.click(); | ||
|
||
const thirdTreeItem = page.locator('.tree-item-title').nth(2); | ||
await expect(thirdTreeItem).toBeVisible(); | ||
await thirdTreeItem.click(); | ||
|
||
const codeSnippets = page.locator('[data-testid="test-snippet"]'); | ||
await expect(codeSnippets.first()).toBeVisible(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is better to use |
||
|
||
// click checkbox to hide snippets | ||
await toggleCheckbox.click(); | ||
await expect(codeSnippets.first()).not.toBeVisible(); | ||
|
||
// click checkbox again to show code snippets | ||
await toggleCheckbox.click(); | ||
await expect(codeSnippets.first()).toBeVisible(); | ||
}); | ||
}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The copyright seems to omit empty lines for some reason.