Skip to content

Commit

Permalink
E2E: Fix Gutenberg publish flow flakiness (#52089)
Browse files Browse the repository at this point in the history
- Addressed the issue where Payment button causes content change so the page needs to be updated right after being published (#50302)
- Made sure that the published page is ensured to be viewed when the `visit` prop is passed
- Removed obsolete `closePanel` prop. No need to close the post-publish panel if we just want to go to the published page/post - the link is in that panel. The panel will be closed, however, if we choose not to visit the published page :)
- Created the `waitUntilElementStopsMoving` helper. It is utilized via the `clickWhenClickable` helper, which should now ensure that we're not attempting to click moving elements. This should generally improve the flakiness caused by interacting with animated elements. The follow-up to this might be #52326. 
- Tweaked the revert-to-draft test where an obsolete assertion was used
- Removed unnecessary _waits_ and _sleeps_ 😴
  • Loading branch information
lsl authored Apr 28, 2021
1 parent fc6f836 commit e0fb410
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 62 deletions.
1 change: 0 additions & 1 deletion test/e2e/lib/components/notifications-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export default class NotificationsComponent extends AsyncBaseContainer {
async trashComment() {
const trashPostLocator = by.css( 'button[title="Trash comment"]' );

await this.driver.sleep( 400 ); // Wait for menu animation to complete
await driverHelper.clickWhenClickable( this.driver, trashPostLocator );
}

Expand Down
41 changes: 40 additions & 1 deletion test/e2e/lib/driver-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const until = {
`for element to be clickable ${ locatorStr }`,
async function ( driver ) {
try {
const element = await driver.findElement( locator );
const element = await waitUntilElementStopsMoving( driver, locator );
const isEnabled = await element.isEnabled();
const isAriaEnabled = await element
.getAttribute( 'aria-disabled' )
Expand Down Expand Up @@ -597,3 +597,42 @@ export function waitUntilAbleToSwitchToWindow( driver, windowIndex, timeout = ex
timeout
);
}

/**
* Waits until an element stops moving. Useful for interacting with animated
* elements.
*
* @param {WebDriver} driver The parent WebDriver instance
* @param {By} locator The element's locator
* @param {number} [timeout=explicitWaitMS] The timeout in milliseconds
* @returns {Promise<WebElement>} A promise that will be resolved with
* the located element
*/
export function waitUntilElementStopsMoving( driver, locator, timeout = explicitWaitMS ) {
const locatorStr = typeof locator === 'function' ? 'by function()' : locator + '';
let elementX;
let elementY;

return driver.wait(
new Condition( `for an element to stop moving ${ locatorStr }`, async function () {
try {
const element = await driver.findElement( locator );
const elementRect = await driver.executeScript(
`return arguments[0].getBoundingClientRect()`,
element
);

if ( elementX !== elementRect.x || elementY !== elementRect.y ) {
elementX = elementRect.x;
elementY = elementRect.y;
return null;
}

return element;
} catch {
return null;
}
} ),
timeout
);
}
74 changes: 24 additions & 50 deletions test/e2e/lib/gutenberg/gutenberg-editor-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer {
'.editor-post-publish-panel__toggle[aria-disabled="false"]'
);
this.publishButtonSelector = By.css(
'.editor-post-publish-panel__header-publish-button button.editor-post-publish-button[aria-disabled="false"]'
'.editor-post-publish-panel__header-publish-button button.editor-post-publish-button'
);
this.publishingSpinnerSelector = By.css(
'.editor-post-publish-panel__content .components-spinner'
Expand Down Expand Up @@ -66,54 +66,44 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer {
return await this.closeSidebar();
}

async publish( { visit = false, closePanel = true } = {} ) {
async publish( { visit = false } = {} ) {
await driverHelper.clickWhenClickable( this.driver, this.prePublishButtonSelector );
await driverHelper.clickWhenClickable( this.driver, this.publishButtonSelector );

// When publishing request completes, the close button appears.
// We use the existence of the close button to determine that the publishing request is completed
// before moving on to the next step.
await driverHelper.waitUntilLocatedAndVisible(
const publishedPostLinkSelector = By.css( '.post-publish-panel__postpublish-header a' );
const publishedPostLinkElement = await driverHelper.waitUntilLocatedAndVisible(
this.driver,
this.closePublishPanelButtonSelector
publishedPostLinkSelector
);

if ( closePanel ) {
try {
await this.closePublishedPanel();
} catch ( e ) {
console.log( 'Publish panel already closed' );
}
}

await this.waitForSuccessViewPostNotice();

const snackBarNoticeLinkSelector = By.css( '.components-snackbar__content a' );
const url = await this.driver.findElement( snackBarNoticeLinkSelector ).getAttribute( 'href' );
const publishedPostLinkUrl = await publishedPostLinkElement.getAttribute( 'href' );

if ( visit ) {
await driverHelper.clickWhenClickable( this.driver, snackBarNoticeLinkSelector );
await driverHelper.clickWhenClickable( this.driver, publishedPostLinkSelector );
await driverHelper.waitUntilLocatedAndVisible( this.driver, By.css( '#page' ) );
} else {
// Close the panel if we're not visiting the published page
await driverHelper.clickWhenClickable(
this.driver,
By.css( 'button[aria-label="Close panel"]' )
);
}

await this.driver.sleep( 1000 );
await driverHelper.acceptAlertIfPresent( this.driver );
return url;
return publishedPostLinkUrl;
}

async update( { visit = false } = {} ) {
await this.driver.sleep( 3000 );
await driverHelper.clickWhenClickable(
this.driver,
By.css( 'button.editor-post-publish-button' )
);

if ( visit ) {
await this.waitForSuccessViewPostNotice();
await this.driver.sleep( 1000 );
return await driverHelper.clickWhenClickable(
await driverHelper.clickWhenClickable(
this.driver,
By.css( '.components-snackbar__content a' )
);
await driverHelper.waitUntilLocatedAndVisible( this.driver, By.css( '#page' ) );
}
}

Expand Down Expand Up @@ -581,31 +571,15 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer {
}

async revertToDraft() {
const revertDraftSelector = By.css( 'button.editor-post-switch-to-draft' );
await driverHelper.clickWhenClickable( this.driver, revertDraftSelector );
const revertAlert = await this.driver.switchTo().alert();
await revertAlert.accept();
await this.waitForSuccessViewPostNotice();
await driverHelper.waitUntilLocatedAndVisible(
this.driver,
By.css( 'button.editor-post-publish-panel__toggle' )
const revertToDraftButtonLocator = By.css( 'button.editor-post-switch-to-draft' );
const enabledPublishButtonLocator = By.css(
'button.editor-post-publish-button__button[aria-disabled="false"]'
);
return await driverHelper.waitTillNotPresent(
this.driver,
By.css( 'button.editor-post-switch-to-draft' )
);
}

async isDraft() {
const hasPublishButton = await driverHelper.isElementPresent(
this.driver,
By.css( 'button.editor-post-publish-panel__toggle' )
);
const hasRevertButton = await driverHelper.isElementPresent(
this.driver,
By.css( 'button.editor-post-switch-to-draft' )
);
return hasPublishButton && ! hasRevertButton;
await driverHelper.clickWhenClickable( this.driver, revertToDraftButtonLocator );
await driverHelper.acceptAlertIfPresent( this.driver );
await driverHelper.waitTillNotPresent( this.driver, revertToDraftButtonLocator );
await driverHelper.waitUntilLocatedAndVisible( this.driver, enabledPublishButtonLocator );
}

async viewPublishedPostOrPage() {
Expand Down
2 changes: 0 additions & 2 deletions test/e2e/lib/gutenberg/gutenberg-editor-sidebar-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,8 @@ export default class GutenbergEditorSidebarComponent extends AsyncBaseContainer
this.driver,
By.css( '.edit-post-post-schedule__toggle' )
);
await this.driver.sleep( 400 ); // Wait for the calendar popup animation
// schedulePost post for the first day of the next month
await driverHelper.clickWhenClickable( this.driver, nextMonthSelector );
await this.driver.sleep( 400 ); // Wait for the month slide animation
await driverHelper.selectElementByText( this.driver, firstDay, '1' );
// Add another click so the calendar modal disappears and makes space for
// the follow-up clicks. This is because of a bug reported in
Expand Down
1 change: 1 addition & 0 deletions test/e2e/lib/pages/frontend/comments-area-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class CommentsAreaComponent extends AsyncBaseContainer {

await driverHelper.scrollIntoView( this.driver, submitButton );
await driverHelper.setWhenSettable( this.driver, commentField, comment );
await driverHelper.scrollIntoView( this.driver, submitButton );
await driverHelper.clickWhenClickable( this.driver, submitButton );
}

Expand Down
17 changes: 14 additions & 3 deletions test/e2e/specs-gutenberg/wp-calypso-gutenberg-page-editor-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,20 @@ describe( `[${ host }] Calypso Gutenberg Editor: Pages (${ screenSize })`, funct

step( 'Can publish and view content', async function () {
const gEditorComponent = await GutenbergEditorComponent.Expect( driver );
// closePanel is set to false because the panel is forcibly dismissed after publishing.
// See https://github.com/Automattic/wp-calypso/issues/50302.
return await gEditorComponent.publish( { closePanel: false, visit: true } );
try {
await gEditorComponent.publish( { visit: true } );
} catch {
/**
* Publish panel is forcibly dismissed after publishing post with a
* Payment button. For some reason Gutenberg detects a change to the
* content so we need to update it and then visit the published page.
*
* This fallback should be removed once the following is resolved:
*
* @see {@link https://github.com/Automattic/wp-calypso/issues/50302}
*/
await gEditorComponent.update( { visit: true } );
}
} );

step( 'Can see the payment button in our published page', async function () {
Expand Down
19 changes: 14 additions & 5 deletions test/e2e/specs-gutenberg/wp-calypso-gutenberg-post-editor-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -998,9 +998,20 @@ describe( `[${ host }] Calypso Gutenberg Editor: Posts (${ screenSize })`, funct

step( 'Can publish and view content', async function () {
const gEditorComponent = await GutenbergEditorComponent.Expect( driver );
// closePanel is set to false because the panel is forcibly dismissed after publishing.
// See https://github.com/Automattic/wp-calypso/issues/50302.
return await gEditorComponent.publish( { closePanel: false, visit: true } );
try {
await gEditorComponent.publish( { visit: true } );
} catch {
/**
* Publish panel is forcibly dismissed after publishing post with a
* Payment button. For some reason Gutenberg detects a change to the
* content so we need to update it and then visit the published page.
*
* This fallback should be removed once the following is resolved:
*
* @see {@link https://github.com/Automattic/wp-calypso/issues/50302}
*/
await gEditorComponent.update( { visit: true } );
}
} );

step( 'Can see the payment button in our published post', async function () {
Expand Down Expand Up @@ -1097,8 +1108,6 @@ describe( `[${ host }] Calypso Gutenberg Editor: Posts (${ screenSize })`, funct
const gHeaderComponent = await GutenbergEditorComponent.Expect( driver );
await gHeaderComponent.dismissSuccessNotice();
await gHeaderComponent.revertToDraft();
const isDraft = await gHeaderComponent.isDraft();
assert.strictEqual( isDraft, true, 'The post is not set as draft' );
} );
} );

Expand Down

0 comments on commit e0fb410

Please sign in to comment.