2024-08-13 11:08:38 +02:00
import { test , expect , type Page , type Locator } from '@playwright/test' ;
2024-05-22 09:59:58 +02:00
import { waitForRestart } from './restart' ;
import pluralize from 'pluralize' ;
import { kebabCase } from 'lodash/fp' ;
2024-02-06 12:26:06 +01:00
2024-08-13 11:08:38 +02:00
type NavItem = string | [ string , string ] | Locator ;
2024-02-06 12:26:06 +01:00
/ * *
* Execute a test suite only if the condition is true
* /
export const describeOnCondition = ( shouldDescribe : boolean ) = >
shouldDescribe ? test.describe : test.describe.skip ;
2024-02-29 14:32:37 +01:00
2024-08-13 11:08:38 +02:00
/ * *
* Find an element in the dom after the previous element
* Useful for narrowing down which link to click when there are multiple with the same name
* /
// TODO: instead of siblingText + linkText, accept an array of any number items
export const locateFirstAfter = async ( page : Page , firstText : string , secondText : string ) = > {
// It first searches for text containing "firstText" then uses xpath `following` to find "secondText" after it.
// `translate` is used to make the search case-insensitive
const item = page
. locator (
` xpath=//text()[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), " ${ firstText . toLowerCase ( ) } ")]/following::a[starts-with(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), " ${ secondText . toLowerCase ( ) } ")] `
)
. first ( ) ;
return item ;
} ;
2024-02-29 14:32:37 +01:00
/ * *
* Navigate to a page and confirm the header , awaiting each step
* /
2024-08-13 11:08:38 +02:00
export const navToHeader = async ( page : Page , navItems : NavItem [ ] , headerText : string ) = > {
2024-02-29 14:32:37 +01:00
for ( const navItem of navItems ) {
2024-08-13 11:08:38 +02:00
// This handles some common issues
// 1. Uses name^= to only ensure starts with, because for example badge notifications cause "Settings" to really be "Settings 1"
// 2. To avoid duplicates, we accept a locator
// 3. To avoid duplicates and writing complex locators, we accept an array to pass to locateFirstAfter, which matches item0 then finds the next item1 in the dom
let item ;
if ( typeof navItem === 'string' ) {
item = page . locator ( ` role=link[name^=" ${ navItem } "] ` ) . last ( ) ;
} else if ( Array . isArray ( navItem ) ) {
item = await locateFirstAfter ( page , navItem [ 0 ] , navItem [ 1 ] ) ;
} else {
// it's a Locator
item = navItem ;
}
2024-02-29 14:32:37 +01:00
await expect ( item ) . toBeVisible ( ) ;
await item . click ( ) ;
}
2024-08-13 11:08:38 +02:00
// Verify header is correct
2024-02-29 14:32:37 +01:00
const header = page . getByRole ( 'heading' , { name : headerText , exact : true } ) ;
await expect ( header ) . toBeVisible ( ) ;
return header ;
} ;
2024-03-28 18:55:57 +01:00
2024-05-22 09:59:58 +02:00
/ * *
* Skip the tour if the modal is visible
* /
export const skipCtbTour = async ( page : Page ) = > {
const modalSelector = 'role=button[name="Skip the tour"]' ;
try {
await page . waitForSelector ( modalSelector , { timeout : 1000 } ) ;
const modal = page . locator ( modalSelector ) ;
if ( await modal . isVisible ( ) ) {
await modal . click ( ) ;
await expect ( modal ) . not . toBeVisible ( ) ;
}
} catch ( e ) {
// The modal did not appear, continue with the test
}
} ;
2024-03-28 18:55:57 +01:00
/ * *
* Look for an element containing text , and then click a sibling close button
* /
export const findAndClose = async (
page : Page ,
text : string ,
role : string = 'status' ,
closeLabel : string = 'Close'
) = > {
// Verify the popup text is visible.
2024-06-17 12:50:43 +02:00
const elements = page . locator ( ` :has-text(" ${ text } ")[role=" ${ role } "] ` ) ;
await expect ( elements . first ( ) ) . toBeVisible ( ) ; // expect at least one element
2024-03-28 18:55:57 +01:00
2024-06-17 12:50:43 +02:00
// Find all 'Close' buttons that are siblings of the elements containing the specified text.
const closeBtns = page . locator (
2024-04-25 16:17:23 +01:00
` :has-text(" ${ text } ")[role=" ${ role } "] ~ button:has-text(" ${ closeLabel } ") `
2024-03-28 18:55:57 +01:00
) ;
2024-06-17 12:50:43 +02:00
// Click all 'Close' buttons.
const count = await closeBtns . count ( ) ;
for ( let i = 0 ; i < count ; i ++ ) {
await closeBtns . nth ( i ) . click ( ) ;
}
2024-03-28 18:55:57 +01:00
} ;
2024-05-22 09:59:58 +02:00
export const createSingleType = async ( page , data ) = > {
const { name , singularId , pluralId } = data ;
await page . getByRole ( 'button' , { name : 'Create new single type' } ) . click ( ) ;
await expect ( page . getByRole ( 'heading' , { name : 'Create a single type' } ) ) . toBeVisible ( ) ;
const displayName = page . getByLabel ( 'Display name' ) ;
await displayName . fill ( name ) ;
const singularIdField = page . getByLabel ( 'API ID (Singular)' ) ;
await expect ( singularIdField ) . toHaveValue ( singularId || kebabCase ( name ) ) ;
if ( singularId ) {
singularIdField . fill ( singularId ) ;
}
const pluralIdField = page . getByLabel ( 'API ID (Plural)' ) ;
await expect ( pluralIdField ) . toHaveValue ( pluralId || pluralize ( kebabCase ( name ) ) ) ;
if ( pluralId ) {
pluralIdField . fill ( pluralId ) ;
}
await page . getByRole ( 'button' , { name : 'Continue' } ) . click ( ) ;
// Create an initial text field for it
await expect ( page . getByText ( 'Select a field for your single type' ) ) . toBeVisible ( ) ;
await page . getByText ( 'Small or long text' ) . click ( ) ;
await page . getByLabel ( 'Name' , { exact : true } ) . fill ( 'myattribute' ) ;
await page . getByRole ( 'button' , { name : 'Finish' } ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Save' } ) . click ( ) ;
await waitForRestart ( page ) ;
await expect ( page . getByRole ( 'heading' , { name } ) ) . toBeVisible ( ) ;
} ;
export const createCollectionType = async ( page , data ) = > {
const { name , singularId , pluralId } = data ;
await page . getByRole ( 'button' , { name : 'Create new collection type' } ) . click ( ) ;
await expect ( page . getByRole ( 'heading' , { name : 'Create a collection type' } ) ) . toBeVisible ( ) ;
const displayName = page . getByLabel ( 'Display name' ) ;
await displayName . fill ( name ) ;
const singularIdField = page . getByLabel ( 'API ID (Singular)' ) ;
await expect ( singularIdField ) . toHaveValue ( singularId || kebabCase ( name ) ) ;
if ( singularId ) {
singularIdField . fill ( singularId ) ;
}
const pluralIdField = page . getByLabel ( 'API ID (Plural)' ) ;
await expect ( pluralIdField ) . toHaveValue ( pluralId || pluralize ( kebabCase ( name ) ) ) ;
if ( pluralId ) {
pluralIdField . fill ( pluralId ) ;
}
await page . getByRole ( 'button' , { name : 'Continue' } ) . click ( ) ;
// Create an initial text field for it
await expect ( page . getByText ( 'Select a field for your collection type' ) ) . toBeVisible ( ) ;
await page . getByText ( 'Small or long text' ) . click ( ) ;
await page . getByLabel ( 'Name' , { exact : true } ) . fill ( 'myattribute' ) ;
await page . getByRole ( 'button' , { name : 'Finish' } ) . click ( ) ;
await page . getByRole ( 'button' , { name : 'Save' } ) . click ( ) ;
await waitForRestart ( page ) ;
await expect ( page . getByRole ( 'heading' , { name } ) ) . toBeVisible ( ) ;
} ;