Skip to content

Commit

Permalink
test(playwright-ct): add to tests (#4876)
Browse files Browse the repository at this point in the history
* test(playwright-ct): improve PT-input decorator test

* test(playwright-ct): simplify assertion

* test(playwright-ct): add missing await statement

* test(playwright-ct): test if overlay is there first

This makes it possible to call these helpers at any time.

* test(playwright-ct): use standard playwright keyboard hotkey activation

After resolving an issue with togglign marks recently, this can now be called the standard way without problems.

* test(playwright-ct): fix issue with flaky webkit tests

Refactor PT-iput overlay activation to reuse code, and get
around an issue on Webkit/Linux where tests would randomly
fail because of the field is not ready before we start
to interact with it.

By adding this:

```ts
await page.waitForSelector(`[data-testid='${testId}']`)`
```

before starting to interact, the problems seems gone.

* test(playwright-ct): remove debug statement

* test(playwright-ct): simplify selectors

Keeping it simple

* test(playwright-ct): decrease expect timout from 20 > 5

* test(playwright-ct): adjust timouts

* test(playwright-ct): ensure visibility of locator

* test(playwright-ct): wait for toolbar to become ready

* test(playwright-ct): add to Decorator test

* test(playwright-ct): use toBeVisible asserts in PT-input placeholder tests

* test(form/inputs): add testid to PT-input toolbar buttons

* test(playwright-ct): fix issue with decorator test on webkit
  • Loading branch information
skogsmaskin authored and rexxars committed Sep 5, 2023
1 parent 886b9cd commit f437d2f
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 78 deletions.
4 changes: 2 additions & 2 deletions packages/sanity/playwright-ct.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export default defineConfig({
],

/* Maximum time one test can run for. */
timeout: 120 * 1000,
timeout: 10 * 1000,
expect: {
// Maximum time expect() should wait for the condition to be met.
timeout: 20 * 1000,
timeout: 5 * 1000,
},

/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ test.describe('Portable Text Input', () => {
// Backtrack and click link icon in menu bar
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('Shift+ArrowLeft+ArrowLeft+ArrowLeft+ArrowLeft')
await page
.getByRole('button')
.filter({has: page.locator('[data-sanity-icon="link"]')})
.click()

await page.getByRole('button', {name: 'Link'}).click()
// Assertion: Wait for link to be re-rendered / PTE internal state to be done
await expect($pte.locator('span[data-link]')).toBeVisible()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable max-nested-callbacks */
import Os from 'os'
import {expect, test} from '@playwright/experimental-ct-react'
import React from 'react'
import {testHelpers} from '../../../utils/testHelpers'
Expand All @@ -10,76 +9,105 @@ const DEFAULT_DECORATORS = [
name: 'strong',
title: 'Strong',
hotkey: 'b',
icon: 'bold',
},
{
name: 'em',
title: 'Italic',
hotkey: 'i',
icon: 'italic',
},
{
name: 'underline',
title: 'Underline',
hotkey: 'u',
icon: 'underline',
},
{
name: 'code',
title: 'Code',
hotkey: "'",
icon: 'code',
},
{
name: 'strike',
title: 'Strike',
hotkey: undefined, // Currently not defined
icon: 'strikethrough',
},
]

test.describe('Portable Text Input', () => {
test.describe('Decorators', () => {
test.describe('Keyboard shortcuts', () => {
test.beforeEach(({browserName}) => {
test.skip(
browserName === 'webkit' && Os.platform() === 'linux',
'Skipping Webkit for Linux which currently is flaky with this test.',
)
test('Render default decorators with keyboard shortcuts', async ({mount, page}) => {
const {
getModifierKey,
getFocusedPortableTextEditor,
getFocusedPortableTextInput,
insertPortableText,
toggleHotkey,
} = testHelpers({
page,
})
test('Render default styles with keyboard shortcuts', async ({mount, page}) => {
const {getModifierKey, getFocusedPortableTextEditor, insertPortableText, toggleHotkey} =
testHelpers({
page,
})
await mount(<DecoratorsStory />)
const $pte = await getFocusedPortableTextEditor('field-body')
const modifierKey = getModifierKey()
await mount(<DecoratorsStory />)
const $portableTextInput = await getFocusedPortableTextInput('field-defaultDecorators')
const $pte = await getFocusedPortableTextEditor('field-defaultDecorators')
const modifierKey = getModifierKey()

// eslint-disable-next-line max-nested-callbacks
for (const decorator of DEFAULT_DECORATORS) {
if (decorator.hotkey) {
await toggleHotkey(decorator.hotkey, modifierKey)
await insertPortableText(`${decorator.name} text 123`, $pte)
await toggleHotkey(decorator.hotkey, modifierKey)
await expect(
$pte.locator(`[data-mark="${decorator.name}"]`, {
hasText: `${decorator.name} text 123`,
}),
).toBeVisible()
}
for (const decorator of DEFAULT_DECORATORS) {
if (decorator.hotkey) {
// Turn on the decorator
await toggleHotkey(decorator.hotkey, modifierKey)
// Assertion: button was toggled
await expect(
$portableTextInput.locator(
`button[data-testid="action-button-${decorator.name}"][data-selected]:not([disabled])`,
),
).toBeVisible()
// Insert some text
await insertPortableText(`${decorator.name} text 123`, $pte)
// Turn off the decorator
await toggleHotkey(decorator.hotkey, modifierKey)
// Assertion: button was toggled
await expect(
$portableTextInput.locator(
`button[data-testid="action-button-${decorator.name}"]:not([data-selected]):not([disabled])`,
),
).toBeVisible()
// Assertion: text has the correct decorator value
await expect(
$pte.locator(`[data-mark="${decorator.name}"]`, {
hasText: `${decorator.name} text 123`,
}),
).toBeVisible()
}
})
}
})

test.describe('Toolbar', () => {
test.describe('Toolbar buttons', () => {
test('Should display all default decorator buttons', async ({mount, page}) => {
const {getFocusedPortableTextInput} = testHelpers({page})
await mount(<DecoratorsStory />)
const $portableTextInput = await getFocusedPortableTextInput('field-body')
const $portableTextInput = await getFocusedPortableTextInput('field-defaultDecorators')

// Assertion: All buttons in the menu bar should be visible
// Assertion: All buttons in the menu bar should be visible and have icon
for (const decorator of DEFAULT_DECORATORS) {
await expect(
$portableTextInput.getByRole('button', {name: decorator.title}),
).toBeVisible()
const $button = $portableTextInput.getByRole('button', {name: decorator.title})
await expect($button).toBeVisible()
await expect($button.locator(`svg[data-sanity-icon='${decorator.icon}']`)).toBeVisible()
}
})

test('Should display custom decorator button and icon', async ({mount, page}) => {
const {getFocusedPortableTextInput} = testHelpers({page})
await mount(<DecoratorsStory />)
const $portableTextInput = await getFocusedPortableTextInput('field-customDecorator')
// Assertion: Button for highlight should exist
const $highlightButton = $portableTextInput.getByRole('button', {name: 'Highlight'})
await expect($highlightButton).toBeVisible()
// Assertion: Icon for highlight should exist
await expect($highlightButton.locator(`svg[data-sanity-icon='bulb-outline']`)).toBeVisible()
})
})
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {defineArrayMember, defineField, defineType} from '@sanity/types'
import React from 'react'
import {BulbOutlineIcon} from '@sanity/icons'
import {TestWrapper} from '../../utils/TestWrapper'

const SCHEMA_TYPES = [
Expand All @@ -10,13 +11,25 @@ const SCHEMA_TYPES = [
fields: [
defineField({
type: 'array',
name: 'body',
name: 'defaultDecorators',
of: [
defineArrayMember({
type: 'block',
}),
],
}),
defineField({
type: 'array',
name: 'customDecorator',
of: [
defineArrayMember({
type: 'block',
marks: {
decorators: [{title: 'Highlight', value: 'highlight', icon: BulbOutlineIcon}],
},
}),
],
}),
],
}),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ test.describe('Portable Text Input', () => {
const $pte = await getFocusedPortableTextEditor('field-body')
const $placeholder = $pte.getByTestId('pt-input-placeholder')
// Assertion: placeholder is there
await expect($placeholder).toBeVisible()
await expect($placeholder).toHaveText('Empty')
// Write some text
await insertPortableText('Hello there', $pte)
// Assertion: placeholder was removed
expect(await $placeholder.count()).toEqual(0)
await expect($placeholder).not.toBeVisible()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ test.describe('Portable Text Input', () => {

const $portableTextInput = await getFocusedPortableTextInput('field-body')

await page
.getByRole('button')
.filter({hasText: /^Object$/})
// @todo It seems like Firefox has different focus behaviour when using keypress here
// causing the focus assertion to fail. The insert button will stay focused even after the dialog opens.
// .press('Enter', {delay: DEFAULT_TYPE_DELAY})
.click()
await page.getByRole('button', {name: 'Insert Object (block)'}).click()

// Assertion: Object preview should be visible
await expect($portableTextInput.locator('.pt-block.pt-object-block')).toBeVisible()
Expand All @@ -28,13 +22,7 @@ test.describe('Portable Text Input', () => {
await mount(<ObjectBlockStory />)
const $pte = await getFocusedPortableTextEditor('field-body')

await page
.getByRole('button')
.filter({hasText: /^Inline Object$/})
// @todo It seems like Firefox has different focus behaviour when using keypress here
// causing the focus assertion to fail. The insert button will stay focused even after the dialog opens.
// .press('Enter', {delay: DEFAULT_TYPE_DELAY})
.click()
await page.getByRole('button', {name: 'Insert Inline Object (inline)'}).click()

// Assertion: Object preview should be visible
await expect($pte.getByTestId('inline-preview')).toBeVisible()
Expand All @@ -49,10 +37,7 @@ test.describe('Portable Text Input', () => {

const $pte = await getFocusedPortableTextEditor('field-body')

await page
.getByRole('button')
.filter({hasText: /^Object$/})
.click()
await page.getByRole('button', {name: 'Insert Object (block)'}).click()

// Assertion: Object preview should be visible
await expect($pte.locator('.pt-block.pt-object-block')).toBeVisible()
Expand Down Expand Up @@ -81,10 +66,7 @@ test.describe('Portable Text Input', () => {

const $portableTextField = await getFocusedPortableTextInput('field-body')

await page
.getByRole('button')
.filter({hasText: /^Object$/})
.click()
await page.getByRole('button', {name: 'Insert Object (block)'}).click()

// Assertion: Object preview should be visible
await expect($portableTextField.locator('.pt-block.pt-object-block')).toBeVisible()
Expand Down Expand Up @@ -138,10 +120,7 @@ test.describe('Portable Text Input', () => {

const $pte = await getFocusedPortableTextEditor('field-body')

await page
.getByRole('button')
.filter({hasText: /^Object$/})
.click()
await page.getByRole('button', {name: 'Insert Object (block)'}).click()

// Assertion: Object preview should be visible
await expect($pte.locator('.pt-block.pt-object-block')).toBeVisible()
Expand Down
31 changes: 22 additions & 9 deletions packages/sanity/playwright-ct/tests/utils/testHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export const TYPE_DELAY_HIGH = 150
export type MountResult = Awaited<ReturnType<ComponentFixtures['mount']>>

export function testHelpers({page}: {page: PlaywrightTestArgs['page']}) {
const activatePTInputOverlay = async ($pteField: Locator) => {
const $overlay = $pteField.getByTestId('activate-overlay')
if (await $overlay.isVisible()) {
await $overlay.focus()
await page.keyboard.press('Space')
}
await $pteField
.locator(`[data-testid='pt-editor__toolbar-card']`)
.waitFor({state: 'visible', timeout: 1000})
}
return {
/**
* Returns the DOM element of a focused Portable Text Input ready to typed into
Expand All @@ -20,12 +30,14 @@ export function testHelpers({page}: {page: PlaywrightTestArgs['page']}) {
* @returns The Portable Text Input element
*/
getFocusedPortableTextInput: async (testId: string) => {
// Wait for field to get ready (without this tests fails randomly on Webkit)
await page.locator(`[data-testid='${testId}']`).waitFor()
const $pteField: Locator = page.getByTestId(testId)
// Activate the input
await $pteField.getByTestId('activate-overlay').focus()
await page.keyboard.press('Space')
// Activate the input if needed
await activatePTInputOverlay($pteField)
// Ensure focus on the contentEditable element of the Portable Text Editor
const $pteTextbox = $pteField.getByRole('textbox')
await $pteTextbox.isEditable()
await $pteTextbox.focus()
return $pteField
},
Expand All @@ -38,12 +50,14 @@ export function testHelpers({page}: {page: PlaywrightTestArgs['page']}) {
* @returns The PT-editor's contentEditable element
*/
getFocusedPortableTextEditor: async (testId: string) => {
// Wait for field to get ready (without this tests fails randomly on Webkit)
await page.locator(`[data-testid='${testId}']`).waitFor()
const $pteField: Locator = page.getByTestId(testId)
// Activate the input
await $pteField.getByTestId('activate-overlay').focus()
await page.keyboard.press('Space')
// Activate the input if needed
await activatePTInputOverlay($pteField)
// Ensure focus on the contentEditable element of the Portable Text Editor
const $pteTextbox = $pteField.getByRole('textbox')
await $pteTextbox.isEditable()
await $pteTextbox.focus()
return $pteTextbox
},
Expand Down Expand Up @@ -82,16 +96,15 @@ export function testHelpers({page}: {page: PlaywrightTestArgs['page']}) {
}),
)
}, text)
await locator.getByText(text).waitFor()
},
/**
* Will create a keyboard event of a given hotkey combination that can be activated with a modifier key
* @param hotkey - the hotkey
* @param modifierKey - the modifier key (if any) that can activate the hotkey
*/
toggleHotkey: async (hotkey: string, modifierKey?: string) => {
if (modifierKey) await page.keyboard.down(modifierKey)
await page.keyboard.press(hotkey)
if (modifierKey) await page.keyboard.up(modifierKey)
await page.keyboard.press(modifierKey ? `${modifierKey}+${hotkey}` : hotkey)
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const ActionMenu = memo(function ActionMenu(props: ActionMenuProps) {
const active = activeKeys.includes(action.key)
return (
<CollapseMenuButton
data-testid={`action-button-${action.key}`}
disabled={disabled || annotationDisabled}
{...COLLAPSE_BUTTON_PROPS}
dividerBefore={action.firstInGroup}
Expand Down

2 comments on commit f437d2f

@vercel
Copy link

@vercel vercel bot commented on f437d2f Sep 5, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

performance-studio – ./

performance-studio.sanity.build
performance-studio-git-next.sanity.build

@vercel
Copy link

@vercel vercel bot commented on f437d2f Sep 5, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

test-studio – ./

test-studio-git-next.sanity.build
test-studio.sanity.build

Please sign in to comment.