Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
98521f3
Add e2e tests for AnchoredOverlay with feature flag variants
siddharthkp Mar 9, 2026
6c91a3b
Update AnchoredOverlay e2e tests
siddharthkp Mar 9, 2026
606b3c9
Add CSS anchor positioning stories and e2e tests
siddharthkp Mar 9, 2026
2fcd605
Fix Fullscreen Variant button name in e2e test
siddharthkp Mar 9, 2026
0f814d5
test(vrt): update snapshots
siddharthkp Mar 9, 2026
23b25b3
test(AnchoredOverlay): reduce VRT tests to light theme only [skip ci]
siddharthkp Mar 9, 2026
02a55be
Merge branch 'main' into anchored-overlay-e2e-tests
siddharthkp Mar 9, 2026
33e1c44
test(vrt): update snapshots
siddharthkp Mar 9, 2026
f185ba9
test(AnchoredOverlay): remove non-light theme snapshots
siddharthkp Mar 9, 2026
b9ed6e1
test(ActionList): finish tooltip animations before screenshot
siddharthkp Mar 9, 2026
119ac95
test(ActionList): restore tooltip snapshot from main
siddharthkp Mar 9, 2026
24251d1
test(vrt): update snapshots
siddharthkp Mar 9, 2026
0c831e4
test(AnchoredOverlay): wait for images before screenshot
siddharthkp Mar 9, 2026
dfb8f35
test(vrt): update snapshots
siddharthkp Mar 9, 2026
c59e516
Revert unrelated snapshot change
siddharthkp Mar 9, 2026
ef646f9
Reset dialog scroll position before screenshot
siddharthkp Mar 10, 2026
f6ad2b0
Revert "Reset dialog scroll position before screenshot"
siddharthkp Mar 10, 2026
450d80f
test(vrt): update snapshots
siddharthkp Mar 10, 2026
28e44fd
Add scroll position debugging to dialog screenshots
siddharthkp Mar 10, 2026
e89111b
within dialog: more stable even if a little incorrect snapshots
siddharthkp Mar 10, 2026
8d1be53
remove flaky tests
siddharthkp Mar 10, 2026
e0555d9
Simplify AnchoredOverlay e2e tests, remove problematic dialog tests
siddharthkp Mar 10, 2026
b059e72
copy e2e/ActionMenu.test.ts as well
siddharthkp Mar 10, 2026
f469574
generate snapshots for ActionMenu
siddharthkp Mar 10, 2026
db791d5
Merge branch 'main' into anchored-overlay-e2e-tests
primer[bot] Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small thing, but I wonder if we would want to have this overlay on top of the header (in the z-index sense) instead of underneath it? 👀

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should, but that's the bug 😄 that Tyler is fixing with anchored position (next PR). I'm just making sure we have tests to not have any regressions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, ha! Makes sense 😂

11 changes: 8 additions & 3 deletions e2e/components/ActionList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,10 @@ test.describe('ActionList', () => {
const inlineItem = page.getByRole('button', {name: /Inline Description/}).first()
await inlineItem.focus()

// Tooltip uses popover attribute; wait for it to become visible
await expect(page.locator('[popover]:popover-open')).toBeVisible()
// Tooltip uses popover attribute; wait for it to become visible and finish animating
const tooltip = page.locator('[popover]:popover-open')
await expect(tooltip).toBeVisible()
await tooltip.evaluate(el => el.getAnimations().map(a => a.finish()))
await expect(page).toHaveScreenshot(`ActionList.Truncated Inline Tooltip.${theme}.png`)
})

Expand All @@ -196,7 +198,10 @@ test.describe('ActionList', () => {
const complexItem = page.getByRole('button', {name: /Description with truncation and complex children/})
await complexItem.focus()

await expect(page.locator('[popover]:popover-open')).toBeVisible()
// Wait for tooltip to become visible and finish animating
const tooltip = page.locator('[popover]:popover-open')
await expect(tooltip).toBeVisible()
await tooltip.evaluate(el => el.getAnimations().map(a => a.finish()))
await expect(page).toHaveScreenshot(`ActionList.Truncated Complex Tooltip.${theme}.png`)
})
}
Expand Down
46 changes: 28 additions & 18 deletions e2e/components/ActionMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,32 +59,42 @@ const stories: Array<{
},
] as const

const featureFlagVariants = [
{flagEnabled: false, suffix: ''},
{flagEnabled: true, suffix: '.css-anchor-positioning'},
] as const

test.describe('ActionMenu', () => {
for (const story of stories) {
test.describe(story.title, () => {
for (const theme of themes) {
test.describe(theme, () => {
test('default @vrt', async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
},
})
for (const {flagEnabled, suffix} of featureFlagVariants) {
test(`default @vrt${suffix}`, async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
featureFlags: {
primer_react_css_anchor_positioning: flagEnabled,
},
},
})

const buttonName = story.buttonName ?? 'Open menu'
const buttonName = story.buttonName ?? 'Open menu'

// Default state
// Open state
// Default state
// Open state

if (!story.skipOpen) {
await page.locator('button', {hasText: buttonName}).waitFor()
await page.getByRole('button', {name: buttonName}).click()
}
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`ActionMenu.${story.title}.${theme}.png`,
)
})
if (!story.skipOpen) {
await page.locator('button', {hasText: buttonName}).waitFor()
await page.getByRole('button', {name: buttonName}).click()
}
expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`ActionMenu.${story.title}.${theme}${suffix}.png`,
)
})
}
})
}
})
Expand Down
173 changes: 173 additions & 0 deletions e2e/components/AnchoredOverlay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import {test, expect} from '@playwright/test'
import {visit} from '../test-helpers/storybook'
import {viewports} from '../test-helpers/viewports'
import {waitForImages} from '../test-helpers/waitForImages'

const stories: Array<{
title: string
id: string
viewport?: keyof typeof viewports
waitForText?: string
buttonName?: string
openDialog?: boolean
openNestedDialog?: boolean
}> = [
// Default
{
title: 'Default',
id: 'components-anchoredoverlay--default',
},
// Features
{
title: 'Portal Inside Scrolling Element',
id: 'components-anchoredoverlay-features--portal-inside-scrolling-element',
},
{
title: 'Custom Anchor Id',
id: 'components-anchoredoverlay-features--custom-anchor-id',
},
{
title: 'Height',
id: 'components-anchoredoverlay-features--height',
},
{
title: 'Width',
id: 'components-anchoredoverlay-features--width',
},
{
title: 'Anchor Alignment',
id: 'components-anchoredoverlay-features--anchor-alignment',
},
{
title: 'Anchor Side',
id: 'components-anchoredoverlay-features--anchor-side',
},
{
title: 'Offset Position From Anchor',
id: 'components-anchoredoverlay-features--offset-position-from-anchor',
},
{
title: 'Offset Alignment From Anchor',
id: 'components-anchoredoverlay-features--offset-alignment-from-anchor',
},
{
title: 'Focus Trap Overrides',
id: 'components-anchoredoverlay-features--focus-trap-overrides',
},
{
title: 'Focus Zone Overrides',
id: 'components-anchoredoverlay-features--focus-zone-overrides',
},
{
title: 'Overlay Props Overrides',
id: 'components-anchoredoverlay-features--overlay-props-overrides',
},
{
title: 'Fullscreen Variant',
id: 'components-anchoredoverlay-features--fullscreen-variant',
viewport: 'primer.breakpoint.xs',
buttonName: 'Open Fullscreen on Narrow',
},
{
title: 'Anchor Position Grid',
id: 'components-anchoredoverlay-features--anchor-position-grid',
buttonName: 'Anchor',
},
{
title: 'Scroll With Anchor',
id: 'components-anchoredoverlay-features--scroll-with-anchor',
buttonName: 'Open Overlay',
},
// {
// title: 'Within Dialog',
// id: 'components-anchoredoverlay-features--within-dialog',
// buttonName: 'Open Overlay',
// openDialog: true,
// },
// {
// title: 'Within Nested Dialog',
// id: 'components-anchoredoverlay-features--within-nested-dialog',
// buttonName: 'Open Overlay',
// openDialog: true,
// openNestedDialog: true,
// },
// {
// title: 'Within Dialog Overflowing',
// id: 'components-anchoredoverlay-features--within-dialog-overflowing',
// buttonName: 'Open Overlay',
// openDialog: true,
// },
{
title: 'Within Sticky Element',
id: 'components-anchoredoverlay-features--within-sticky-element',
buttonName: 'Open Overlay',
},
// Dev
{
title: 'Reposition After Content Grows',
id: 'components-anchoredoverlay-dev--reposition-after-content-grows',
waitForText: 'content with 300px height',
},
{
title: 'Reposition After Content Grows Within Dialog',
id: 'components-anchoredoverlay-dev--reposition-after-content-grows-within-dialog',
waitForText: 'content with 300px height',
},
] as const

const theme = 'light'

test.describe('AnchoredOverlay', () => {
for (const story of stories) {
test.describe(story.title, () => {
for (const withCSSAnchorPositioning of [false, true]) {
const namePostfix = withCSSAnchorPositioning ? '.css-anchor-positioning' : ''

test(`default @vrt${namePostfix ? ` ${namePostfix}` : ''}`, async ({page}) => {
await visit(page, {
id: story.id,
globals: {
colorScheme: theme,
...(withCSSAnchorPositioning && {
featureFlags: {
primer_react_css_anchor_positioning: true,
},
}),
},
})

if (story.viewport) {
await page.setViewportSize({
width: viewports[story.viewport],
height: 667,
})
}

// Open dialog if needed
if (story.openDialog) {
await page.getByRole('button', {name: 'Open Dialog'}).click()
}

// Open nested dialog if needed
if (story.openNestedDialog) {
await page.getByRole('button', {name: 'Open Inner Dialog'}).click()
}

// Open the overlay
const buttonName = story.buttonName ?? 'Button'
await page.locator('button', {hasText: buttonName}).first().waitFor()
const overlayButton = page.getByRole('button', {name: buttonName}).first()
await overlayButton.click()

// for the dev stories, we intentionally change the content after the overlay is open to test that it repositions correctly
if (story.waitForText) await page.getByText(story.waitForText).waitFor()
await waitForImages(page)

expect(await page.screenshot({animations: 'disabled'})).toMatchSnapshot(
`AnchoredOverlay.${story.title}.${theme}${namePostfix}.png`,
)
})
}
})
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,95 @@
.Icon {
color: var(--fgColor-muted);
}

.CenteredTrigger {
display: flex;
align-items: center;
justify-content: center;
min-height: 300vh;
/* stylelint-disable-next-line primer/responsive-widths -- intentionally large for scroll testing */
min-width: 300vw;
}

.AnchorGridContainer {
overflow: auto;
height: 100vh;
width: 100vw;
}

.AnchorGrid {
display: flex;
align-items: center;
justify-content: center;
min-height: 200vh;
/* stylelint-disable-next-line primer/responsive-widths -- intentionally large for grid layout testing */
min-width: 200vw;
}

.AnchorGridCell {
display: flex;
align-items: center;
justify-content: center;
/* stylelint-disable-next-line primer/colors -- fallback value for borderColor-default */
border: var(--borderWidth-thin) solid var(--borderColor-default, #d1d9e0);
padding: var(--base-size-16);
position: relative;
height: 200px;
}

.AnchorGridLabel {
font-size: var(--text-body-size-small);
line-height: var(--text-body-lineHeight-small);
color: var(--fgColor-muted);
position: absolute;
top: var(--base-size-8);
left: var(--base-size-8);
}

.AnchorGridInner {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
width: 50%;
height: 50%;
}

.ScrollContainer {
height: 400px;
overflow: auto;
/* stylelint-disable-next-line primer/colors -- fallback value for borderColor-default */
border: var(--borderWidth-thin) solid var(--borderColor-default, #d1d9e0);
border-radius: var(--borderRadius-medium);
position: relative;
}

.ScrollContent {
height: 1200px;
display: flex;
align-items: center;
justify-content: center;
padding: var(--base-size-16);
}

.StickyHeader {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--base-size-8) var(--base-size-16);
background-color: var(--bgColor-default);
/* stylelint-disable-next-line primer/colors -- fallback value for borderColor-default */
border-bottom: var(--borderWidth-thin) solid var(--borderColor-default, #d1d9e0);
}

.StickyScrollArea {
height: 1200px;
padding: var(--base-size-16);
}

.DialogBody {
padding: var(--base-size-16);
height: 2000px;
}
Loading
Loading