UNPKG

@plone/volto

Version:
995 lines (906 loc) 27.8 kB
/* eslint-disable no-console */ import '@testing-library/cypress/add-commands'; import { getIfExists } from '../helpers'; import { ploneAuth } from './constants'; const HOSTNAME = Cypress.env('BACKEND_HOST') || '127.0.0.1'; const GUILLOTINA_API_URL = `http://${HOSTNAME}:8081/db/web`; const PLONE_SITE_ID = Cypress.env('SITE_ID') || 'plone'; const PLONE_API_URL = Cypress.env('API_PATH') || `http://${HOSTNAME}:55001/${PLONE_SITE_ID}`; const SLATE_SELECTOR = '.content-area .slate-editor [contenteditable=true]'; const SLATE_TITLE_SELECTOR = '.block.inner.title [contenteditable="true"]'; const TABLE_SLATE_SELECTOR = '.celled.fixed.table tbody tr:nth-child(1) td:first-child() [contenteditable="true"]'; const TABLE_HEAD_SLATE_SELECTOR = '.celled.fixed.table thead tr th:first-child() [contenteditable="true"]'; const ploneAuthObj = { user: ploneAuth[0], pass: ploneAuth[1], }; // --- isInViewport ---------------------------------------------------------- Cypress.Commands.add('isInViewport', (element) => { cy.get(element).then(($el) => { const windowInnerWidth = Cypress.config(`viewportWidth`); const windowInnerHeight = Cypress.config(`viewportHeight`); const rect = $el[0].getBoundingClientRect(); const rightBoundOfWindow = windowInnerWidth; const bottomBoundOfWindow = windowInnerHeight; expect(rect.top).to.be.at.least(0); expect(rect.left).to.be.at.least(0); expect(rect.right).to.be.lessThan(rightBoundOfWindow); expect(rect.bottom).to.be.lessThan(bottomBoundOfWindow); }); }); // --- isInHTML ---------------------------------------------------------- Cypress.Commands.add('isInHTML', ({ parent = 'body', content }) => { cy.url().then((currentUrl) => { // sometimes the cy command is called when the url is still at content/edit // we want to query the html markup of the content, not the edit form const url = currentUrl.indexOf('/edit') !== -1 ? currentUrl.split('/edit')[0] : currentUrl; cy.request({ method: 'GET', url: url, }).then((response) => { const html = Cypress.$(response.body); if (content.startsWith('.') || content.startsWith('#')) { return expect(html.find(parent)).to.have.descendants(content); } else { // check if parent contains the content text string in its HTML output return expect(html.find(parent)).to.contain(content); } }); }); }); // --- AUTOLOGIN ------------------------------------------------------------- Cypress.Commands.add('autologin', (usr, pass) => { let api_url, user, password; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; user = usr || 'admin'; password = pass || 'admin'; } else { api_url = PLONE_API_URL; user = usr || ploneAuth[0]; password = pass || ploneAuth[1]; } return cy .request({ method: 'POST', url: `${api_url}/@login`, headers: { Accept: 'application/json' }, body: { login: user, password: password }, }) .then((response) => cy.setCookie('auth_token', response.body.token)); }); // --- CREATE CONTENT -------------------------------------------------------- Cypress.Commands.add( 'createContent', ({ contentType, contentId, contentTitle, contentDescription, path = '', allow_discussion = false, transition = '', bodyModifier = (body) => body, image = false, }) => { let api_url, auth; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; auth = { user: 'root', pass: 'root', }; } else { api_url = PLONE_API_URL; auth = ploneAuthObj; } const defaultParams = { method: 'POST', url: `${api_url}/${path}`, headers: { Accept: 'application/json', }, auth: auth, body: { '@type': contentType, id: contentId, title: contentTitle, description: contentDescription, allow_discussion: allow_discussion, }, }; if (contentType === 'File') { const params = { ...defaultParams, body: bodyModifier({ ...defaultParams.body, file: { data: 'dGVzdGZpbGUK', encoding: 'base64', filename: 'lorem.txt', 'content-type': 'text/plain', }, }), }; return cy.request(params); } if (contentType === 'Image') { const params = { ...defaultParams, body: bodyModifier({ ...defaultParams.body, image: { data: 'iVBORw0KGgoAAAANSUhEUgAAANcAAAA4CAMAAABZsZ3QAAAAM1BMVEX29fK42OU+oMvn7u9drtIPisHI4OhstdWZyt4fkcXX5+sAg74umMhNp86p0eJ7vNiKw9v/UV4wAAAAAXRSTlMAQObYZgAABBxJREFUeF7tmuty4yAMhZG4X2zn/Z92J5tsBJwWXG/i3XR6frW2Y/SBLIRAfaQUDNt8E5tLUt9BycfcKfq3R6Mlfyimtx4rzp+K3dtibXkor99zsEqLYZltblTecciogoh+TXfY1Ve4dn07rCDGG9dHSEEOg/GmXl0U1XDxTKxNK5De7BxsyyBr6gGm2/vPxKJ8F6f7BXKfRMp1xIWK9A+5ks25alSb353dWnDJN1k35EL5f8dVGifTf/4tjUuuFq7u4srmXC60yAmldLXIWbg65RKU87lcGxJCFqUPv0IacW0PmSivOZFLE908inPToMmii/roG+MRV/O8FU88i8tFsxV3a06MFUw0Qu7RmAtdV5/HVVaOVMTWNOWSwMljLhzhcB6XIS7OK5V6AvRDNN7t5VJWQs1J40UmalbK56usBG/CuCHSYuc+rkUGeMCViNRARPrzW52N3oQLe6WifNliSuuGaH3czbVNudI9s7ZLUCLHVwWlyES522o1t14uvmbblmVTKqFjaZYJFSTPP4dLL1kU1z7p0lzdbRulmEWLxoQX+z9ce7A8GqEEucllLxePuZwdJl1Lezu0hoswvTPt61DrFcRuujV/2cmlxaGBC7Aw6cpovGANwRiSdOAWJ5AGy4gLL64dl0QhUEAuEUNws+XxV+OKGPdw/hESGYF9XEGaFC7sNLMSXWJjHsnanYi87VK428N2uxpOjOFANcagLM5l+7mSycM8KknZpKLcGi6jmzWGr/vLurZ/0g4u9AZuAoeb5r1ceQhyiTPY1E4wUR6u/F3H2ojSpXMMriBPT9cezTto8Cx+MsglHL4fv1Rxrb1LVw9yvyQpJ3AhFnLZfuRLH2QsOG3FGGD20X/th/u5bFAt16Bt308KjF+MNOXgl/SquIEySX3GhaZvc67KZbDxcCDORz2N8yCWPaY5lyQZO7lQ29fnZbt3Xu6qoge4+DjXl/MocySPOp9rlvdyznahRyHEYd77v3LhugOXDv4J65QXfl803BDAdaWBEDhfVx7nKofjoVCgxnUAqw/UAUDPn788BDvQuG4TDtdtUPvzjSlXAB8DvaDOhhrmhwbywylXAm8CvaouikJTL93gs3y7Yy4VYbIxOHrcMizPqWOjqO9l3Uz52kibQy4xxOgqhJvD+w5rvokOcAlGvNCfeqCv1ste1stzLm0f71Iq3ZfTrPfuE5nhPtF+LvQE2lffQC7pYtQy3tdzdrKvd5TLVVzDetScS3nEKmmwDyt1Cev1kX3YfbvzNK4fzrlw+cB6vm+uiUgf2zdXI62241LawCb7Pi5FXFPF8KpzDoF/Sw2lg+GrHNbno1mhPu+VCF/vfMnw06PnUl6j48dVHD3jHNHPua+fc3o/5yp/zsGi0vYtzi3Pz5mHd4T6BWMIlewacd63AAAAAElFTkSuQmCC', encoding: 'base64', filename: 'image.png', 'content-type': 'image/png', }, }), }; return cy.request(params); } if ( ['Document', 'News Item', 'Folder', 'CMSFolder'].includes(contentType) ) { const params = { ...defaultParams, body: { ...defaultParams.body, blocks: { 'd3f1c443-583f-4e8e-a682-3bf25752a300': { '@type': 'title' }, '7624cf59-05d0-4055-8f55-5fd6597d84b0': { '@type': 'slate' }, }, blocks_layout: { items: [ 'd3f1c443-583f-4e8e-a682-3bf25752a300', '7624cf59-05d0-4055-8f55-5fd6597d84b0', ], }, }, }; if (image) { let sourceFilename = 'cypress/fixtures/halfdome2022.jpg'; let imageObject = { encoding: 'base64', filename: 'image.jpg', 'content-type': 'image/jpg', }; if (typeof image === 'object') { sourceFilename = image.sourceFilename; imageObject = { ...imageObject, ...image, }; } cy.readFile(sourceFilename, 'base64').then((encodedImage) => { const withImageParams = { ...params, body: bodyModifier({ ...params.body, preview_image: { ...imageObject, data: encodedImage, }, }), }; return cy.request(withImageParams).then(() => { if (transition) { cy.setWorkflow({ path: path || contentId, review_state: transition, }); } console.log(`${contentType} created`); }); }); } else { const documentParams = { ...params, body: bodyModifier({ ...params.body, }), }; return cy.request(documentParams).then(() => { if (transition) { cy.setWorkflow({ path: path || contentId, review_state: transition, }); } console.log(`${contentType} created`); }); } } else { return cy .request({ method: 'POST', url: `${api_url}/${path}`, headers: { Accept: 'application/json', }, auth: auth, body: bodyModifier({ '@type': contentType, id: contentId, title: contentTitle, allow_discussion: allow_discussion, }), }) .then(() => { if (transition) { cy.setWorkflow({ path: path || contentId, review_state: transition, }); } console.log(`${contentType} created`); }); } }, ); // Remove content Cypress.Commands.add('removeContent', ({ path = '' }) => { let api_url, auth; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; auth = { user: 'root', pass: 'root', }; } else { api_url = PLONE_API_URL; auth = ploneAuthObj; } return cy.request({ method: 'DELETE', url: `${api_url}/${path}`, headers: { Accept: 'application/json', }, auth: auth, }); }); // Get content Cypress.Commands.add('getContent', ({ path = '' }) => { let api_url, auth; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; auth = { user: 'root', pass: 'root', }; } else { api_url = PLONE_API_URL; auth = ploneAuthObj; } return cy.request({ method: 'get', url: `${api_url}/${path}`, headers: { Accept: 'application/json', }, auth: auth, }); }); // --- Add DX Content-Type ---------------------------------------------------------- Cypress.Commands.add('addContentType', (name) => { let api_url, auth; api_url = Cypress.env('API_PATH') || 'http://127.0.0.1:8080/Plone'; auth = ploneAuthObj; return cy .request({ method: 'POST', url: `${api_url}/@controlpanels/dexterity-types/${name}`, headers: { Accept: 'application/json', }, auth: auth, body: { title: name, }, }) .then(() => console.log(`${name} content-type added.`)); }); // --- Remove DX behavior ---------------------------------------------------------- Cypress.Commands.add('removeContentType', (name) => { let api_url, auth; api_url = Cypress.env('API_PATH') || 'http://127.0.0.1:8080/Plone'; auth = ploneAuthObj; return cy .request({ method: 'DELETE', url: `${api_url}/@controlpanels/dexterity-types/${name}`, headers: { Accept: 'application/json', }, auth: auth, body: {}, }) .then(() => console.log(`${name} content-type removed.`)); }); // --- Add DX field ---------------------------------------------------------- Cypress.Commands.add('addSlateJSONField', (type, name) => { let api_url, auth; api_url = Cypress.env('API_PATH') || 'http://127.0.0.1:8080/Plone'; auth = ploneAuthObj; return cy .request({ method: 'POST', url: `${api_url}/@types/${type}`, headers: { Accept: 'application/json', }, auth: auth, body: { id: name, title: name, description: 'Slate JSON Field', factory: 'SlateJSONField', required: false, }, }) .then(() => console.log(`${name} SlateJSONField field added to ${type}`)); }); // --- Remove DX field ---------------------------------------------------------- Cypress.Commands.add('removeSlateJSONField', (type, name) => { let api_url, auth; api_url = Cypress.env('API_PATH') || 'http://127.0.0.1:8080/Plone'; auth = ploneAuthObj; return cy .request({ method: 'DELETE', url: `${api_url}/@types/${type}/${name}`, headers: { Accept: 'application/json', }, auth: auth, body: {}, }) .then(() => console.log(`${name} SlateJSONField field removed from ${type}`), ); }); // --- CREATE USER -------------------------------------------------------- Cypress.Commands.add( 'createUser', ({ username = 'editor', fullname = 'editor', email = 'editor@local.dev', password = 'password', roles = ['Member', 'Reader', 'Editor'], groups = { '@id': 'http://127.0.0.1:3000/@users', items: [ { id: 'AuthenticatedUsers', title: 'AuthenticatedUsers', }, ], items_total: 1, }, }) => { let api_url, auth, path; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; auth = { user: 'root', pass: 'root', }; path = 'users'; } else { api_url = PLONE_API_URL; auth = ploneAuthObj; path = '@users'; } return cy .request({ method: 'POST', url: `${api_url}/${path}`, headers: { Accept: 'application/json', }, auth: auth, body: { '@type': 'User', username: username, fullname: fullname, email: email, password: password, roles: roles, groups: groups, }, }) .then(() => console.log(`User ${username} created`)); }, ); // Remove user Cypress.Commands.add('removeUser', (username = 'editor') => { let api_url, auth, path; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; auth = { user: 'root', pass: 'root', }; path = 'users'; } else { api_url = PLONE_API_URL; auth = ploneAuthObj; path = '@users'; } return cy .request({ method: 'DELETE', url: `${api_url}/${path}/${username}`, headers: { Accept: 'application/json', }, auth: auth, }) .then(() => console.log(`User ${username} removed`)); }); // --- GROUP ----------------------------------------------------------------- Cypress.Commands.add( 'createGroup', ({ groupname = 'teachers', email = 'teachers@local.dev', password = ploneAuth[1], roles = ['Member', 'Reader'], users = { '@id': 'http://127.0.0.1:3000/@groups', items: [], items_total: 0, }, }) => { let api_url, auth, path; if (Cypress.env('API') === 'guillotina') { api_url = GUILLOTINA_API_URL; auth = { user: 'root', pass: 'root', }; path = 'groups'; } else { api_url = PLONE_API_URL; auth = ploneAuthObj; path = '@groups'; } return cy .request({ method: 'POST', url: `${api_url}/${path}`, headers: { Accept: 'application/json', }, auth: auth, body: { '@type': 'Group', groupname: groupname, email: email, password: password, roles: roles, users: users, }, }) .then(() => console.log(`Group ${groupname} created`)); }, ); // --- SET WORKFLOW ---------------------------------------------------------- Cypress.Commands.add( 'setWorkflow', ({ path = '/', actor = 'admin', review_state = 'publish', time = '1995-07-31T18:30:00', title = '', comment = '', effective = '2018-01-21T08:00:00', expires = '2019-01-21T08:00:00', include_children = true, }) => { let api_url, auth; api_url = PLONE_API_URL; auth = ploneAuthObj; return cy.request({ method: 'POST', url: `${api_url}/${path}/@workflow/${review_state}`, headers: { Accept: 'application/json', }, auth: auth, body: { actor: actor, review_state: review_state, time: time, title: title, comment: comment, effective: effective, expires: expires, include_children: include_children, }, }); }, ); Cypress.Commands.add('waitForResourceToLoad', (fileName, type) => { const resourceCheckInterval = 40; const maxChecks = 50; const count = [0]; return new Cypress.Promise((resolve) => { const checkIfResourceHasBeenLoaded = () => { const resource = cy .state('window') .performance.getEntriesByType('resource') .filter((entry) => !type || entry.initiatorType === type) .find((entry) => entry.name.includes(fileName)); if (resource) { resolve(); return; } count[0] += 1; const tid = setTimeout( checkIfResourceHasBeenLoaded, resourceCheckInterval, ); if (count[0] > maxChecks) { clearTimeout(tid); throw new Error( `Timeout resolving resource: ${fileName} (type ${type})`, ); } }; checkIfResourceHasBeenLoaded(); }); }); // --- CREATE CONTENT -------------------------------------------------------- Cypress.Commands.add('setRegistry', (record, value) => { let api_url, auth; api_url = PLONE_API_URL; auth = ploneAuthObj; return cy.request({ method: 'PATCH', url: `${api_url}/@registry/`, headers: { Accept: 'application/json', }, auth: auth, body: { [record]: value, }, }); }); // Low level command reused by `setSelection` and low level command `setCursor` Cypress.Commands.add('selection', { prevSubject: true }, (subject, fn) => { cy.wrap(subject) .trigger('mousedown') .wait(1000) //multiple waits between selecting the text to ensure toolbar is visible. .then(fn) .wait(1000) .trigger('mouseup'); cy.document().trigger('selectionchange'); return cy.wrap(subject); }); Cypress.Commands.add( 'setSelection', { prevSubject: true }, (subject, query, endQuery) => { return cy.wrap(subject).selection(($el) => { if (typeof query === 'string') { const anchorNode = getTextNode($el[0], query); const focusNode = endQuery ? getTextNode($el[0], endQuery) : anchorNode; const anchorOffset = anchorNode.wholeText.indexOf(query); const focusOffset = endQuery ? focusNode.wholeText.indexOf(endQuery) + endQuery.length : anchorOffset + query.length; setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); } else if (typeof query === 'object') { const el = $el[0]; const anchorNode = getTextNode(el.querySelector(query.anchorQuery)); const anchorOffset = query.anchorOffset || 0; const focusNode = query.focusQuery ? getTextNode(el.querySelector(query.focusQuery)) : anchorNode; const focusOffset = query.focusOffset || 0; setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); } }); }, ); // Low level command reused by `setCursorBefore` and `setCursorAfter`, equal to `setCursorAfter` Cypress.Commands.add( 'setCursor', { prevSubject: true }, (subject, query, atStart) => { return cy.wrap(subject).selection(($el) => { const node = getTextNode($el[0], query); const offset = node.wholeText.indexOf(query) + (atStart ? 0 : query.length); const document = node.ownerDocument; document.getSelection().removeAllRanges(); document.getSelection().collapse(node, offset); }); // Depending on what you're testing, you may need to chain a `.click()` here to ensure // further commands are picked up by whatever you're testing (this was required for Slate, for example). }, ); Cypress.Commands.add( 'setCursorBefore', { prevSubject: true }, (subject, query) => { cy.wrap(subject).setCursor(query, true); }, ); Cypress.Commands.add( 'setCursorAfter', { prevSubject: true }, (subject, query) => { cy.wrap(subject).setCursor(query); }, ); Cypress.Commands.add( 'pasteClipboard', { prevSubject: true }, (query, htmlContent) => { return cy.wrap(query).trigger('paste', createHtmlPasteEvent(htmlContent)); }, ); Cypress.Commands.add('toolbarSave', () => { // Save cy.get('#toolbar-save', { timeout: 10000 }).click(); cy.waitForResourceToLoad(''); }); Cypress.Commands.add('clearSlate', (selector) => { return cy .get(selector) .focus() .click({ force: true }) .type('{selectAll}', { delay: 20 }) .type('{backspace}', { delay: 20 }) .wait(200); }); Cypress.Commands.add('getSlate', (createNewSlate = false) => { let slate; if (createNewSlate) { cy.get('.block.inner').last().type('{moveToEnd}{enter}'); } cy.getIfExists( SLATE_SELECTOR, () => { slate = cy.get(SLATE_SELECTOR).last().should('be.visible'); }, () => { slate = cy .get(SLATE_SELECTOR, { timeout: 10000 }) .last() .should('be.visible'); }, ); return slate; }); Cypress.Commands.add('getSlateSelector', (selector = SLATE_SELECTOR) => { let slate; cy.getIfExists( selector, () => { slate = cy.get(selector).last(); }, () => { slate = cy.get(selector, { timeout: 10000 }).last(); }, ); return slate; }); Cypress.Commands.add('getSlateTitle', () => { return cy.get(SLATE_TITLE_SELECTOR, { timeout: 10000, }); }); Cypress.Commands.add('clearSlateTitle', () => { return cy.clearSlate(SLATE_TITLE_SELECTOR); }); // Slate commands Cypress.Commands.add('typeInSlate', { prevSubject: true }, (subject, text) => { return ( cy .wrap(subject) .then((subject) => { subject[0].dispatchEvent( new InputEvent('beforeinput', { inputType: 'insertText', data: text, }), ); return subject; }) // TODO: do this only for Electron-based browser which does not understand instantaneously // that the user inserted some text in the block .wait(1000) ); }); Cypress.Commands.add( 'typeWithDelay', { prevSubject: 'element' }, (subject, text, options) => { const delay = options && options.delay ? options.delay : 20; cy.wrap(subject).type(text, { delay }); }, ); Cypress.Commands.add('lineBreakInSlate', { prevSubject: true }, (subject) => { return ( cy .wrap(subject) .then((subject) => { subject[0].dispatchEvent( new InputEvent('beforeinput', { inputType: 'insertLineBreak' }), ); return subject; }) // TODO: do this only for Electron-based browser which does not understand instantaneously // that the block was split .wait(1000) ); }); Cypress.Commands.add( 'setSlateSelection', (subject, query, endQuery, wait = 1000) => { cy.get('.slate-editor.selected [contenteditable=true]') .focus() // .click() .setSelection(subject, query, endQuery) .wait(wait); // this wait is needed for the selection change to be detected after }, ); Cypress.Commands.add('getSlateEditorAndType', (type) => { cy.getSlate().focus().click().type(type); }); Cypress.Commands.add('getSlateEditorSelectorAndType', (selector, type) => { cy.getSlateSelector(selector).focus().click().type(type); }); Cypress.Commands.add('setSlateCursor', (subject, query, endQuery) => { cy.get('.slate-editor.selected [contenteditable=true]') .focus() .click() .setCursor(subject, query, endQuery) .wait(1000); // this wait is needed for the selection change to be detected after }); Cypress.Commands.add('clickSlateButton', (button, timeout = 1000) => { cy.get(`.slate-inline-toolbar .button-wrapper a[title="${button}"]`, { timeout, }).click({ force: true }); // force click is needed to ensure the button in visible in view. }); // Helper functions function getTextNode(el, match) { const walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false); if (!match) { return walk.nextNode(); } let node; while ((node = walk.nextNode())) { if (node.wholeText.includes(match)) { return node; } } } function setBaseAndExtent(...args) { const document = args[0].ownerDocument; document.getSelection().removeAllRanges(); document.getSelection().setBaseAndExtent(...args); } function createHtmlPasteEvent(htmlContent) { return Object.assign( new Event('paste', { bubbles: true, cancelable: true }), { clipboardData: { getData: () => htmlContent, types: ['text/html'], }, }, ); } Cypress.Commands.add('addNewBlock', (blockName, createNewSlate = false) => { let block; block = cy.getSlate(createNewSlate).click().type(`/${blockName}{enter}`); return block; }); Cypress.Commands.add('navigate', (route = '') => { cy.intercept('GET', '**/*').as('navGetCall'); cy.window().its('appHistory').invoke('push', route); cy.wait('@navGetCall'); }); Cypress.Commands.add('store', () => { return cy.window().its('store').invoke('getStore', ''); }); Cypress.Commands.add('settings', (key, value) => { return cy.window().its('settings'); }); Cypress.Commands.add('getIfExists', getIfExists); Cypress.Commands.add('getTableSlate', (header = false) => { let slate; cy.addNewBlock('table'); cy.wait(2000); const selector = header ? TABLE_HEAD_SLATE_SELECTOR : TABLE_SLATE_SELECTOR; cy.getIfExists( selector, () => { slate = cy.get(selector).last(); }, () => { slate = cy.get(selector, { timeout: 10000 }).last(); }, ); return slate; }); Cypress.Commands.add('configureListingWith', (contentType) => { cy.get('.sidebar-container .tabs-wrapper .menu .item') .contains('Block') .click(); cy.get('.querystring-widget .fields').contains('Add criteria').click(); cy.get( '.querystring-widget .fields:first-of-type .field:first-of-type .react-select__menu .react-select__option', ) .contains('Type') .click(); //insert Page cy.get('.querystring-widget .fields:first-of-type > .field').click(); cy.get( '.querystring-widget .fields:first-of-type > .field .react-select__menu .react-select__option', ) .contains(contentType) .click(); }); Cypress.Commands.add( 'addLocationQuerystring', (option = 'Relative path', value) => { cy.get('.block-editor-listing').click(); cy.get('.querystring-widget .fields').contains('Add criteria').click(); cy.get('.querystring-widget .react-select__menu .react-select__option') .contains('Location') .click(); cy.get('.querystring-widget .fields').contains('Absolute path').click(); cy.get( '.querystring-widget .fields .react-select__menu .react-select__option', ) .contains(option) .click(); if (value) { cy.get('.querystring-widget .fields .input') .click() .type(`${value}{enter}`); } }, ); Cypress.Commands.add('queryCounter', (path, steps, number = 1) => { cy.intercept(path, cy.spy().as('counterName')); steps.forEach((element) => { element(); }); cy.get('@counterName').its('callCount').should('equal', number); }); // Print cypress-axe violations to the terminal function printAccessibilityViolations(violations) { cy.task( 'table', violations.map(({ id, impact, description, nodes }) => ({ impact, description: `${description} (${id})`, nodes: nodes.length, })), ); } Cypress.Commands.add( 'checkAccessibility', (subject, { skipFailures = false } = {}) => { cy.checkA11y(subject, null, printAccessibilityViolations, skipFailures); }, { prevSubject: 'optional', }, );