diff --git a/.github/workflows/e2e-test-wp57.yml b/.github/workflows/e2e-test-wp58.yml similarity index 90% rename from .github/workflows/e2e-test-wp57.yml rename to .github/workflows/e2e-test-wp58.yml index 5d4cec77..1b5549cd 100644 --- a/.github/workflows/e2e-test-wp57.yml +++ b/.github/workflows/e2e-test-wp58.yml @@ -1,4 +1,4 @@ -name: e2e-test-wp57 +name: e2e-test-wp58 on: push: branches: [ master ] @@ -11,9 +11,9 @@ jobs: strategy: fail-fast: false matrix: - containers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] - total-machines: [20] - wp-versions: ['5.7.2'] # Use this specific WP version + containers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # Enumerate the total-machines as a range, this must match the total-machines number below. + total-machines: [10] # The number of concurrent test machines to run. You can lower this when there are only a few tests you know will run (max 20). + wp-versions: ['5.8.3'] # Use this specific WP version steps: - uses: actions/checkout@v2 # Checkout the current branch (e2e). @@ -38,7 +38,7 @@ jobs: # Check the current PHP version installed - name: Check PHP version run: php -v - + # Install all dependencies from composer.json - name: Install dependencies working-directory: Stackable diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 4b902586..223ecf81 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -11,8 +11,8 @@ jobs: strategy: fail-fast: false matrix: - containers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] # Enumerate the total-machines as a range, this must match the total-machines number below. - total-machines: [20] # The number of concurrent test machines to run. You can lower this when there are only a few tests you know will run (max 20). + containers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # Enumerate the total-machines as a range, this must match the total-machines number below. + total-machines: [10] # The number of concurrent test machines to run. You can lower this when there are only a few tests you know will run (max 20). steps: - uses: actions/checkout@v2 # Checkout the current branch (e2e). @@ -37,7 +37,7 @@ jobs: # Check the current PHP version installed - name: Check PHP version run: php -v - + # Install all dependencies from composer.json - name: Install dependencies working-directory: Stackable @@ -87,7 +87,7 @@ jobs: - name: Run Test Suite uses: cypress-io/github-action@v2 with: - command: npm run cy:run:parallel -- --machine-number=${{ matrix.containers }} --total-machines=${{ matrix.total-machines }} --spec=cypress/integration/v3/**/*.spec.js --browser=chrome --env=STACKABLE_PREMIUM_CODE=${{ secrets.STACKABLE_PREMIUM_CODE }},GITHUB_ACTIONS=1 --config=baseUrl=http://localhost:8889 + command: npm run cy:run:parallel -- --machine-number=${{ matrix.containers }} --total-machines=${{ matrix.total-machines }} --spec=cypress/integration/v3/**/*.spec.js --headed --browser=chrome --env=STACKABLE_PREMIUM_CODE=${{ secrets.STACKABLE_PREMIUM_CODE }},GITHUB_ACTIONS=1 --config=baseUrl=http://localhost:8889 # STACKABLE_PREMIUM_CODE contains a premium license key for Stackable Premium - name: Upload E2E Test Results diff --git a/.wp-env.json b/.wp-env.json index 3af4c70d..885c5d05 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,5 +1,10 @@ { "core": "https://wordpress.org/latest.zip", - "themes": [ "WordPress/theme-experiments/tt1-blocks#tt1-blocks@0.4.7" ], - "plugins": [ ".", "./Stackable" ] + "themes": [ + "WordPress/theme-experiments/tt1-blocks#tt1-blocks@0.4.7" + ], + "plugins": [ + ".", + "./Stackable" + ] } diff --git a/cypress/integration/v2/features/optimization-settings.spec.js b/cypress/integration/v2/features/optimization-settings.spec.js index e92311d5..eb418273 100644 --- a/cypress/integration/v2/features/optimization-settings.spec.js +++ b/cypress/integration/v2/features/optimization-settings.spec.js @@ -39,7 +39,7 @@ function optimizationSettings() { cy.visit( previewUrl ) // CSS & JS files should not be present because there are no Stackable blocks. cssJsSelectors.forEach( selector => { - cy.get( selector ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', selector ) } ) cy.visit( editorUrl ) diff --git a/cypress/integration/v2/features/role-manager.spec.js b/cypress/integration/v2/features/role-manager.spec.js index 41f7539b..0f68933a 100644 --- a/cypress/integration/v2/features/role-manager.spec.js +++ b/cypress/integration/v2/features/role-manager.spec.js @@ -19,12 +19,12 @@ function roleManagerTest() { // By default, full site editing mode is set for all roles // Content only mode notice should not be present // Test this for administrator - cy.get( '.ugb-editor-mode-notice' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', '.ugb-editor-mode-notice' ) } ) coreblocks.forEach( blockName => { // Test this for core blocks as well. cy.addBlock( blockName ) - cy.get( '.ugb-editor-mode-notice' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', '.ugb-editor-mode-notice' ) } ) cy.savePost() @@ -57,11 +57,11 @@ function roleManagerTest() { cy.visit( editorUrl ) blocks.forEach( blockName => { cy.selectBlock( blockName ) - cy.get( '.ugb-editor-mode-notice' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', '.ugb-editor-mode-notice' ) } ) coreblocks.forEach( blockName => { cy.selectBlock( blockName ) - cy.get( '.ugb-editor-mode-notice' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', '.ugb-editor-mode-notice' ) } ) cy.savePost() } ) @@ -98,18 +98,18 @@ function roleManagerTest() { cy.openInspector( blockName, 'Style' ) cy.selectBlock( blockName ) cy.get( '.ugb-editor-mode-notice' ).should( 'exist' ) - cy.get( 'button[tooltip="Copy & Paste Styles"]' ).should( 'not.exist' ) - cy.get( 'button[aria-label="Dynamic Fields"]' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', 'button[tooltip="Copy & Paste Styles"]' ) + cy.get( 'body' ).should( 'not.have.descendants', 'button[aria-label="Dynamic Fields"]' ) } ) coreblocks.forEach( blockName => { cy.selectBlock( blockName ) cy.get( '.ugb-editor-mode-notice' ).should( 'exist' ) - cy.get( 'button[tooltip="Copy & Paste Styles"]' ).should( 'not.exist' ) - cy.get( 'button[aria-label="Dynamic Fields"]' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', 'button[tooltip="Copy & Paste Styles"]' ) + cy.get( 'body' ).should( 'not.have.descendants', 'button[aria-label="Dynamic Fields"]' ) } ) - cy.get( 'button[aria-label="Stackable Settings"]' ).should( 'not.exist' ) - cy.get( 'button[aria-label="Open Design Library"]' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', 'button[aria-label="Stackable Settings"]' ) + cy.get( 'body' ).should( 'not.have.descendants', 'button[aria-label="Open Design Library"]' ) // The blocklist should only contain 4 text blocks. // 2 of which are Stackable blocks. diff --git a/cypress/integration/v3/blocks/other-tests.spec.js b/cypress/integration/v3/blocks/other-tests.spec.js index 2b798f35..9042ad17 100644 --- a/cypress/integration/v3/blocks/other-tests.spec.js +++ b/cypress/integration/v3/blocks/other-tests.spec.js @@ -26,7 +26,7 @@ function addInnerBlocksToQueryLoop() { // Filter out button & icon button when adding inner blocks. // We'll add button group instead. stkBlocks - .filter( blockName => ! Array( 'stackable/button', 'stackable/icon-button' ).includes( blockName ) ) + .filter( blockName => ! Array( 'stackable/button', 'stackable/icon-button', 'stackable/icon-list' ).includes( blockName ) ) .forEach( blockName => { cy.addInnerBlock( 'core/post-template', blockName ) } ) diff --git a/cypress/support/gutenberg/commands/assertions.js b/cypress/support/gutenberg/commands/assertions.js index b7d1905b..3b980662 100644 --- a/cypress/support/gutenberg/commands/assertions.js +++ b/cypress/support/gutenberg/commands/assertions.js @@ -103,7 +103,6 @@ export function _assertComputedStyle( selector, pseudoEl, _cssObject, assertType parentEl.parentElement.offsetHeight // eslint-disable-line no-unused-expressions const assertionCallback = ( element, pseudoEl, parentEl ) => { - const computedStyles = Object.assign( {}, pick( win.getComputedStyle( element, pseudoEl || undefined ), ...keys( _cssObject ).map( camelCase ) ) ) const expectedStylesToEnqueue = keys( _cssObject ).map( key => `${ key }: ${ convertExpectedValueForEnqueue( _cssObject[ key ] ) } !important` ).join( '; ' ) @@ -121,6 +120,8 @@ export function _assertComputedStyle( selector, pseudoEl, _cssObject, assertType element.parentNode.insertBefore( dummyStyle, element ) } + const computedStyles = Object.assign( {}, pick( win.getComputedStyle( element, pseudoEl || undefined ), ...keys( _cssObject ).map( camelCase ) ) ) + element.offsetHeight // eslint-disable-line no-unused-expressions element.parentElement.offsetHeight // eslint-disable-line no-unused-expressions parentEl.parentElement.offsetHeight // eslint-disable-line no-unused-expressions @@ -637,7 +638,7 @@ export function assertFrontendStyles( subject, alias ) { // Get the class selector. const classList = ( isParent ? Array.from( blockElement.classList ) - : Array.from( blockElement.querySelector( `.${ innerBlockUniqueClass }` ).classList ) ).map( _class => `.${ _class }` ).join( '' ) + : Array.from( blockElement.querySelector( `.${ innerBlockUniqueClass.replace( / /g, '.' ) }` ).classList ) ).map( _class => `.${ _class }` ).join( '' ) // Remove all blocks inside .entry-content. doc.querySelector( '.entry-content' ).innerHTML = '' diff --git a/cypress/support/gutenberg/commands/blocks.js b/cypress/support/gutenberg/commands/blocks.js index 2c6f6e95..340a9d21 100644 --- a/cypress/support/gutenberg/commands/blocks.js +++ b/cypress/support/gutenberg/commands/blocks.js @@ -27,7 +27,7 @@ Cypress.Commands.add( 'addNewColumn', { prevSubject: true }, addNewColumn ) * Command for asserting block error. */ export function assertBlockError() { - cy.get( '.block-editor-warning' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', '.block-editor-warning' ) } /** @@ -72,6 +72,8 @@ export function addBlock( blockName = 'ugb/accordion', options = {} ) { cy.wp().then( wp => { const block = wp.blocks.createBlock( blockName ) + const { clientId: newBlockId, attributes: { className } } = block + new Cypress.Promise( resolve => { wp.data.dispatch( 'core/editor' ).insertBlock( block ) .then( dispatchResolver( resolve ) ) @@ -83,10 +85,6 @@ export function addBlock( blockName = 'ugb/accordion', options = {} ) { .find( `button[aria-label="${ variation }"]` ) .click( { force: true } ) } - } ) - - cy.wp().then( wp => { - const { clientId: newBlockId, attributes: { className } } = last( wp.data.select( 'core/block-editor' ).getBlocks() ) new Cypress.Promise( resolve => { wp.data.dispatch( 'core/block-editor' ).selectBlock( newBlockId ).then( dispatchResolver( resolve ) ) @@ -247,6 +245,7 @@ export function addInnerBlock( blockName = 'ugb/accordion', blockToAdd = 'ugb/ac if ( $editor.find( '.block-editor-block-variation-picker' ).length ) { cy.get( '.block-editor-block-variation-picker' ) .find( '.block-editor-block-variation-picker__skip button' ) + .first() // There can be multiple buttons. .click( { force: true } ) } } ) diff --git a/cypress/support/gutenberg/commands/controls.js b/cypress/support/gutenberg/commands/controls.js index 0e15c031..dd5115c4 100644 --- a/cypress/support/gutenberg/commands/controls.js +++ b/cypress/support/gutenberg/commands/controls.js @@ -279,10 +279,14 @@ function colorControl( name, value, options = {} ) { cy .get( '.components-popover__content .components-color-picker' ) - .find( 'button[aria-label="Show detailed inputs"]' ) - .click( { force: true } ) - cy - .get( 'input[maxlength="6"]' ) + // Expand the custom color field if it's not expanded. + .then( $el => { + if ( $el.find( 'button[aria-label="Show detailed inputs"]' ).length > 0 ) { + cy.get( 'button[aria-label="Show detailed inputs"]' ).click( { force: true } ) + } + return cy.wrap( $el ) + } ) + .find( 'input.components-input-control__input' ) .type( `{selectall}${ value.replace( '#', '' ) }{enter}`, { force: true } ) selector() diff --git a/cypress/support/gutenberg/commands/editor.js b/cypress/support/gutenberg/commands/editor.js index 16687e5b..e8808079 100644 --- a/cypress/support/gutenberg/commands/editor.js +++ b/cypress/support/gutenberg/commands/editor.js @@ -7,6 +7,72 @@ import { import { setBaseAndExtent } from '../util' import { isArray } from 'lodash' +// In WordPress 5.9, previewing Gutenberg in tablet/mobile now replaces the +// editor area with an iframe. This produces an error because the Cypress get +// command doesn't include the contents of an iframe. +// +// This overwrites the get command to also include the contents of the iframe. +// If the normal get command fails, we try again within the iframe. +Cypress.Commands.overwrite( 'get', ( originalFn, selector, options ) => { + // Turn off logging to help speed up tests. + const newOptions = Object.assign( options || {}, { log: false } ) + + if ( selector === 'iframe[name="editor-canvas"]' || selector === 'body' ) { + return originalFn( selector, newOptions ) + } + + return originalFn( 'body', { log: false } ).then( $body => { + try { + if ( $body.find( selector ).length > 0 ) { + return originalFn( selector, newOptions ) + } + } catch ( $err ) { + return originalFn( selector, newOptions ) + } + + if ( $body.find( 'iframe[name="editor-canvas"]' ).length > 0 ) { + return new Cypress.Promise( resolve => { + originalFn( 'iframe[name="editor-canvas"]' ).then( $iframe => { + const body = $iframe[ 0 ].contentDocument.body + if ( body && body.querySelector( '.block-editor-block-list__layout' ) ) { + const jQueryObj = Cypress.$( body.querySelector( selector ) ) + resolve( jQueryObj ) + } else { + setTimeout( () => { + const body = $iframe[ 0 ].contentDocument.body + const jQueryObj = Cypress.$( body.querySelector( selector ) ) + resolve( jQueryObj ) + }, 200 ) + } + } ) + } ) + } + + return originalFn( selector, newOptions ) + } ) +} ); + +// In connection to the above fix, we also need to override the jQuery find +// command since it also doesn't handle iframes. So when the jQuery find command +// looks for an element that doesn't exist, also retry whether it's found inside +// the editor iframe. +( function( $ ) { + const oldFind = $.fn.find + $.fn.find = function() { + const findResults = oldFind.apply( this, arguments ) + + if ( ! findResults.length ) { + const iframe = oldFind.apply( this, [ 'iframe[name="editor-canvas"]' ] ) + if ( iframe.length ) { + const iframeBody = $( iframe[ 0 ].contentDocument.body ) + return iframeBody.find( arguments[ 0 ] ) + } + } + + return findResults + } +}( Cypress.$ ) ) + /** * Register functions to Cypress Commands. */ @@ -235,7 +301,7 @@ export function wp() { export function typePostTitle( title ) { cy .get( '.edit-post-visual-editor__post-title-wrapper' ) - .find( 'textarea.editor-post-title__input' ) + .find( 'textarea.editor-post-title__input, .editor-post-title__input' ) // In WP 5.9, it's no longer a textarea. .type( `{selectall}${ title }`, { force: true } ) cy.publish() } diff --git a/cypress/support/gutenberg/util.js b/cypress/support/gutenberg/util.js index 0cefd153..bbf780c2 100644 --- a/cypress/support/gutenberg/util.js +++ b/cypress/support/gutenberg/util.js @@ -31,7 +31,8 @@ export function withInspectorTabMemory( options = {} ) { cy.document().then( doc => { const optionsToPass = args.length === argumentLength ? args.pop() : {} - const activePanel = doc.querySelector( 'button.components-panel__body-toggle[aria-expanded="true"]' ).innerText + // Don't include the Navigation panel. + const activePanel = doc.querySelector( '.components-panel__body:not(.ugb-panel--navigation-view) button.components-panel__body-toggle[aria-expanded="true"]' ).innerText // This is for stackable only. // After asserting the frontend, go back to the previous state. diff --git a/cypress/support/stackable/commands/index.js b/cypress/support/stackable/commands/index.js index c20cc8b6..656636b2 100644 --- a/cypress/support/stackable/commands/index.js +++ b/cypress/support/stackable/commands/index.js @@ -6,7 +6,7 @@ import { modifyLogFunc } from '../util' /** * Overwrite Cypress Commands */ -Cypress.Commands.overwrite( 'get', modifyLogFunc() ) +// Cypress.Commands.overwrite( 'get', modifyLogFunc() ) // Don't overwrite the get command, it's overwritten by the Gutenberg get command already. Cypress.Commands.overwrite( 'click', modifyLogFunc() ) Cypress.Commands.overwrite( 'type', modifyLogFunc() ) Cypress.Commands.overwrite( 'reload', modifyLogFunc() ) diff --git a/cypress/support/stackable/commands/inspector.js b/cypress/support/stackable/commands/inspector.js index 565e6a5d..7d37b285 100644 --- a/cypress/support/stackable/commands/inspector.js +++ b/cypress/support/stackable/commands/inspector.js @@ -38,13 +38,15 @@ export function openInspector( subject, tab, selector ) { cy.selectBlock( subject, selector ) cy.toggleSidebar( 'edit-post/block', true ) + // Ensure the block tab is open. cy - .get( 'button.edit-post-sidebar__panel-tab' ) - .contains( containsRegExp( 'Block' ) ) + .get( '.edit-post-sidebar__panel-tabs' ) + .contains( 'button', 'Block' ) .click( { force: true } ) + // Open the Stackable tab we want. cy - .get( `button[aria-label="${ tab } Tab"]` ) + .get( `button.edit-post-sidebar__panel-tab[aria-label="${ tab } Tab"]` ) .click( { force: true } ) } diff --git a/cypress/support/stackable/helpers/advanced.js b/cypress/support/stackable/helpers/advanced.js index cce1dbba..2670893c 100644 --- a/cypress/support/stackable/helpers/advanced.js +++ b/cypress/support/stackable/helpers/advanced.js @@ -722,7 +722,12 @@ class AdvancedModule extends Module { cy.savePost() cy.getPostUrls().then( ( { editorUrl, previewUrl } ) => { cy.visit( previewUrl ) - cy.get( MAIN_SELECTOR ).should( assertionValue ) + if ( assertionValue === 'exist' ) { + cy.get( MAIN_SELECTOR ).should( assertionValue ) + } else { + // Use this instead because get( MAIN_SELECTOR ) sometimes fails. + cy.get( 'body' ).should( 'not.have.descendants', MAIN_SELECTOR ) + } cy.visit( editorUrl ) selectInspector() } ) diff --git a/cypress/support/wordpress/commands/plugins.js b/cypress/support/wordpress/commands/plugins.js index 68f5fe2c..faad6074 100644 --- a/cypress/support/wordpress/commands/plugins.js +++ b/cypress/support/wordpress/commands/plugins.js @@ -44,5 +44,5 @@ export function activatePlugin( slug ) { * plugin activation. */ export function assertPluginError() { - cy.get( '.xdebug-error' ).should( 'not.exist' ) + cy.get( 'body' ).should( 'not.have.descendants', '.xdebug-error' ) }