-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jetpack E2E: expand the Marketing: SEO spec. #81199
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,14 +8,12 @@ type MarketingPageTab = | |
| 'Connections' | ||
| 'Sharing Buttons' | ||
| 'Business Tools'; | ||
type SEOPageTitleStructureCategories = 'Front Page' | 'Posts' | 'Pages' | 'Tags' | 'Archives'; | ||
type SEOExternalServices = 'Google search' | 'Facebook' | 'Twitter'; | ||
type SocialConnection = 'Facebook' | 'LinkedIn' | 'Tumblr' | 'Mastodon' | 'Instagram Business'; | ||
|
||
const selectors = { | ||
// Traffic tab | ||
websiteMetaTextArea: '#advanced_seo_front_page_description', | ||
seoPreviewButton: '.seo-settings__preview-button', | ||
seoPreviewPane: '.web-preview.is-seo', | ||
seoPreviewPaneCloseButton: '.web-preview__close', | ||
pageTitleStructureInput: '.title-format-editor', | ||
}; | ||
|
||
/** | ||
|
@@ -56,32 +54,65 @@ export class MarketingPage { | |
/** | ||
* Enters text into the Website Meta Information field. | ||
* | ||
* @param {string} [text] String to be used as the description of the web site in SEO. | ||
* @returns {Promise<void>} No return value. | ||
* @param {string} text String to be used as the description of the web site in SEO. | ||
*/ | ||
async enterWebsiteMetaInformation( text = 'test text' ): Promise< void > { | ||
await this.page.fill( selectors.websiteMetaTextArea, text ); | ||
async enterExternalPreviewText( text: string ) { | ||
await this.page.getByRole( 'textbox', { name: 'Front Page Meta Description' } ).fill( text ); | ||
} | ||
|
||
/** | ||
* Open the preview of SEO changes. | ||
* Validates the external preview for the specified service contains the text. | ||
* | ||
* @returns {Promise<void>} No return value. | ||
* @param {SEOExternalServices} service External service. | ||
* @param {string} text Text to validate. | ||
*/ | ||
async openSEOPreview(): Promise< void > { | ||
const locator = this.page.locator( selectors.seoPreviewButton ); | ||
await locator.click(); | ||
await this.page.waitForSelector( selectors.seoPreviewPane ); | ||
async validateExternalPreview( service: SEOExternalServices, text: string ) { | ||
await this.page.locator( '.vertical-menu__social-item' ).filter( { hasText: service } ).click(); | ||
|
||
await this.page.locator( '.seo-preview-pane__preview' ).getByText( text ).waitFor(); | ||
} | ||
|
||
/** | ||
* Given an accessible name of the button, click on the button. | ||
* | ||
* @param {string} name Accessible name of the button. | ||
*/ | ||
async clickButton( name: string ) { | ||
await this.page.getByRole( 'button', { name: name } ).click(); | ||
} | ||
|
||
/** | ||
* Enters the specified text into the input of the specified category, changing the | ||
* page title structure. | ||
* | ||
* @param {SEOPageTitleStructureCategories} category Category to modify. | ||
* @param {string} text Text to enter. | ||
*/ | ||
async enterPageTitleStructure( category: SEOPageTitleStructureCategories, text: string ) { | ||
const target = this.page | ||
.locator( selectors.pageTitleStructureInput ) | ||
.filter( { has: this.page.getByText( category ) } ) | ||
.getByRole( 'textbox' ); | ||
|
||
await target.scrollIntoViewIfNeeded(); | ||
|
||
await target.fill( text ); | ||
} | ||
|
||
/** | ||
* Close the preview of SEO changes. | ||
* Returns the preview text for the page title structure category. | ||
* | ||
* @returns {Promise<void>} No return value. | ||
* @param {SEOPageTitleStructureCategories} category Category to return preview text for. | ||
* @returns {Promise<string>} Preview text. | ||
*/ | ||
async closeSEOPreview(): Promise< void > { | ||
await this.page.click( selectors.seoPreviewPaneCloseButton ); | ||
await this.page.waitForSelector( selectors.seoPreviewButton ); | ||
async getPreviewTextForPageStructureCategory( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion (non-blocking): Definitely not required, but what do you think of designing this function as a I was thinking more of the case where you already have a title, but then you change it. The element will already exist in the DOM, and at that point it's effectively a race against the React re-render. 😆 I think in the vast majority of cases (and we don't even test this case right now), the re-render will be fast enough, but using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, changed to a validate function instead. |
||
category: SEOPageTitleStructureCategories | ||
): Promise< string > { | ||
return await this.page | ||
.locator( selectors.pageTitleStructureInput ) | ||
.filter( { has: this.page.getByText( category ) } ) | ||
.locator( '.title-format-editor__preview' ) | ||
.innerText(); | ||
} | ||
|
||
/* Social Connectisons */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,91 @@ | ||
/** | ||
* @group calypso-pr | ||
* @group jetpack-wpcom-integration | ||
*/ | ||
|
||
import { DataHelper, SidebarComponent, MarketingPage, TestAccount } from '@automattic/calypso-e2e'; | ||
import { | ||
getTestAccountByFeature, | ||
envToFeatureKey, | ||
envVariables, | ||
DataHelper, | ||
SidebarComponent, | ||
MarketingPage, | ||
TestAccount, | ||
} from '@automattic/calypso-e2e'; | ||
import { Page, Browser } from 'playwright'; | ||
|
||
declare const browser: Browser; | ||
|
||
/** | ||
* Quick test to verify various SEO text fields and previews render. | ||
* | ||
* This is a feature exclusive to Business plans and higher. | ||
* | ||
* Keywords: Jetpack, SEO, Traffic, Marketing. | ||
*/ | ||
describe( DataHelper.createSuiteTitle( 'Marketing: SEO Preview' ), function () { | ||
let marketingPage: MarketingPage; | ||
const externalPreviewText = 'Test front page meta description'; | ||
|
||
let page: Page; | ||
let testAccount: TestAccount; | ||
let marketingPage: MarketingPage; | ||
|
||
beforeAll( async () => { | ||
page = await browser.newPage(); | ||
|
||
const testAccount = new TestAccount( 'atomicUser' ); | ||
// Simple sites do not have the ability to change SEO parameters. | ||
const accountName = getTestAccountByFeature( envToFeatureKey( envVariables ), [ | ||
{ | ||
gutenberg: 'stable', | ||
siteType: 'simple', | ||
accountName: 'atomicUser', | ||
}, | ||
{ | ||
gutenberg: 'edge', | ||
siteType: 'simple', | ||
accountName: 'atomicUser', | ||
}, | ||
] ); | ||
testAccount = new TestAccount( accountName ); | ||
await testAccount.authenticate( page ); | ||
|
||
marketingPage = new MarketingPage( page ); | ||
} ); | ||
|
||
it( 'Navigate to Tools > Marketing page', async function () { | ||
const sidebarComponent = new SidebarComponent( page ); | ||
await sidebarComponent.navigate( 'Tools', 'Marketing' ); | ||
if ( envVariables.ATOMIC_VARIATION === 'ecomm-plan' ) { | ||
await marketingPage.visit( testAccount.getSiteURL( { protocol: false } ) ); | ||
} else { | ||
const sidebarComponent = new SidebarComponent( page ); | ||
await sidebarComponent.navigate( 'Tools', 'Marketing' ); | ||
} | ||
} ); | ||
|
||
it( 'Click on Traffic tab', async function () { | ||
marketingPage = new MarketingPage( page ); | ||
await marketingPage.clickTab( 'Traffic' ); | ||
} ); | ||
|
||
it( 'Enter SEO meta description', async function () { | ||
await marketingPage.enterWebsiteMetaInformation(); | ||
it( 'Enter and verify SEO page title front page structure', async function () { | ||
const frontPageText = 'test front page title text'; | ||
await marketingPage.enterPageTitleStructure( 'Front Page', frontPageText ); | ||
|
||
const previewText = await marketingPage.getPreviewTextForPageStructureCategory( 'Front Page' ); | ||
expect( previewText ).toContain( frontPageText ); | ||
} ); | ||
|
||
it( 'Enter SEO external preview description', async function () { | ||
await marketingPage.enterExternalPreviewText( externalPreviewText ); | ||
} ); | ||
|
||
it( 'Open SEO preview', async function () { | ||
await marketingPage.clickButton( 'Show Previews' ); | ||
} ); | ||
|
||
it( 'Verify preview for Facebook', async function () { | ||
await marketingPage.validateExternalPreview( 'Facebook', externalPreviewText ); | ||
} ); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion (non-blocking): How do you feel about adding saving the settings into this test spec? I think it should be easily designable to handle concurrencies, because i think the previews seem to be based on local unsaved state. I figured validating that the save web request goes through okay might be nice addition! But totally not required. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your hunch is correct, and I was able to work saving the changes into the flow. Nice suggestion! |
||
it( 'Open and close SEO preview', async function () { | ||
await marketingPage.openSEOPreview(); | ||
await marketingPage.closeSEOPreview(); | ||
it( 'Close SEO preview', async function () { | ||
await marketingPage.clickButton( 'Close preview' ); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Praise: You've done such a nice job in these PRs updating the locator syntax as you go! I really appreciate that! 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.