-
Notifications
You must be signed in to change notification settings - Fork 49
Add navigation smoke tests #2054
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
base: trunk
Are you sure you want to change the base?
Changes from all commits
d5a42a1
442a4d7
0f94422
e87b14b
609d38d
fbd3e7c
de49ad7
8349a94
e7947ee
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 |
|---|---|---|
| @@ -0,0 +1,347 @@ | ||
| import { test, expect, Page } from '@playwright/test'; | ||
| import fs from 'fs-extra'; | ||
| import { E2ESession } from './e2e-helpers'; | ||
| import Onboarding from './page-objects/onboarding'; | ||
| import SiteContent from './page-objects/site-content'; | ||
| import WhatsNewModal from './page-objects/whats-new-modal'; | ||
| import { getUrlWithAutoLogin } from './utils'; | ||
|
|
||
| /** | ||
| * Closes the WordPress Block Editor welcome guide if it appears. | ||
| * Attempts to close up to 3 times as the modal can appear multiple times. | ||
| */ | ||
| async function closeWelcomeGuide( page: Page ) { | ||
| // Try to close the modal up to 3 times | ||
| for ( let i = 0; i < 2; i++ ) { | ||
| try { | ||
| // Wait for the modal frame to appear | ||
| const modalFrame = page.locator( '.components-modal__frame' ); | ||
| await modalFrame.waitFor( { state: 'visible', timeout: 2000 } ); | ||
|
|
||
| // Find and click the close button using specific selector | ||
| const closeButton = page.locator( '.components-modal__header > button[aria-label="Close"]' ); | ||
| await closeButton.waitFor( { state: 'visible', timeout: 2000 } ); | ||
| await closeButton.click(); | ||
| } catch ( e ) { | ||
| // Modal not found or already closed, exit the loop | ||
| break; | ||
| } | ||
| } | ||
|
Comment on lines
+25
to
+29
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. Code Quality: Empty catch block loses error context The catch block silently swallows the error without logging. While this may be intentional for the modal not appearing, it makes debugging harder if there's an actual error. Consider logging at debug level: } catch ( e ) {
// Modal not found or already closed, exit the loop
// Note: This is expected when the modal doesn't appear
break;
}Or if you have a logger, use it for visibility during test debugging. |
||
| } | ||
|
|
||
| test.describe( 'Site Navigation', () => { | ||
| const session = new E2ESession(); | ||
|
|
||
| const siteName = 'My WordPress Website'; // Use the default site created during onboarding | ||
|
|
||
| let frontendUrl: string; | ||
| let wpAdminUrl: string; | ||
|
|
||
| test.beforeAll( async () => { | ||
| await session.launch(); | ||
|
|
||
| // Complete onboarding before tests | ||
| const onboarding = new Onboarding( session.mainWindow ); | ||
| await expect( onboarding.heading ).toBeVisible(); | ||
| await onboarding.continueButton.click(); | ||
|
|
||
| const whatsNewModal = new WhatsNewModal( session.mainWindow ); | ||
| if ( await whatsNewModal.locator.isVisible( { timeout: 5000 } ) ) { | ||
| await whatsNewModal.closeButton.click(); | ||
| } | ||
|
|
||
| // Wait for default site to be ready and get URLs | ||
| const siteContent = new SiteContent( session.mainWindow, siteName ); | ||
| await expect( siteContent.siteNameHeading ).toBeVisible( { timeout: 120_000 } ); | ||
|
|
||
| // Get site URLs for tests | ||
| const settingsTab = await siteContent.navigateToTab( 'Settings' ); | ||
| wpAdminUrl = await settingsTab.copyWPAdminUrlToClipboard( session.electronApp ); | ||
| frontendUrl = await settingsTab.copySiteUrlToClipboard( session.electronApp ); | ||
| } ); | ||
|
|
||
| test.afterAll( async () => { | ||
| await session.cleanup(); | ||
| } ); | ||
|
|
||
| test( 'opens site at homepage', async ( { page } ) => { | ||
| // Navigate to the site homepage | ||
| await page.goto( frontendUrl ); | ||
|
|
||
| // Verify the page loaded successfully | ||
| await expect( page ).toHaveURL( frontendUrl ); | ||
|
|
||
| // Check for WordPress indicators (body classes, meta tags, etc.) | ||
| const bodyClass = await page.locator( 'body' ).getAttribute( 'class' ); | ||
| expect( bodyClass ).toMatch( /wordpress|wp-|home/ ); | ||
|
|
||
| // Verify page title exists | ||
| const title = await page.title(); | ||
| expect( title ).toBeTruthy(); | ||
| expect( title.length ).toBeGreaterThan( 0 ); | ||
| } ); | ||
|
|
||
| test( 'opens and automatically logs in to WP Admin', async ( { page } ) => { | ||
| // Navigate to wp-admin with auto-login | ||
| await page.goto( getUrlWithAutoLogin( wpAdminUrl ) ); | ||
|
|
||
| // Verify we're on the dashboard | ||
| await expect( page ).toHaveURL( /wp-admin/ ); | ||
|
|
||
| // Check for dashboard elements | ||
| await expect( page.locator( '#wpadminbar' ) ).toBeVisible(); | ||
| await expect( page.locator( '#adminmenuback' ) ).toBeVisible(); | ||
|
|
||
| // Verify we're logged in by checking for user menu | ||
| const userMenu = page.locator( '#wp-admin-bar-my-account' ); | ||
| await expect( userMenu ).toBeVisible(); | ||
| } ); | ||
|
|
||
| test( 'creates a post', async ( { page } ) => { | ||
| // Navigate to new post page | ||
| const newPostUrl = `${ wpAdminUrl }/post-new.php`; | ||
|
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. Cross-Platform: Path concatenation While string concatenation works here for URLs, consider using const newPostUrl = new URL( '/wp-admin/post-new.php', wpAdminUrl ).toString();This ensures proper URL handling even if |
||
| await page.goto( getUrlWithAutoLogin( newPostUrl ) ); | ||
|
|
||
| const editorFrame = page.frameLocator( 'iframe[name="editor-canvas"]' ); | ||
|
|
||
| // Close welcome guide if it appears (always on main page, not in iframe) | ||
| await closeWelcomeGuide( page ); | ||
|
|
||
| // Wait for title to be available in iframe | ||
| const titleSelector = 'h1.editor-post-title'; | ||
| await editorFrame.locator( titleSelector ).waitFor( { timeout: 30_000 } ); | ||
|
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. Performance: Long timeout The 30-second timeout seems excessive for an iframe element to load. Consider:
// Wait for title to be available in iframe (long timeout for slower CI environments)
await editorFrame.locator( titleSelector ).waitFor( { timeout: 30_000 } ); |
||
| await editorFrame.locator( titleSelector ).fill( 'E2E Test Post' ); | ||
|
|
||
| // Click into the content area and type | ||
| await editorFrame.locator( titleSelector ).press( 'Enter' ); | ||
| const contentBlock = editorFrame.locator( 'p[role="document"]' ).first(); | ||
| await contentBlock.fill( 'This is a test post created by automated E2E tests.' ); | ||
|
|
||
| // Publish the post (publish buttons are on main page) | ||
| const publishButton = page.locator( 'button.editor-post-publish-button__button' ).first(); | ||
| await publishButton.waitFor( { state: 'visible', timeout: 10_000 } ); | ||
| await publishButton.click(); | ||
|
Comment on lines
+121
to
+123
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. Flaky Test Risk: Selector fragility Using Consider more specific selectors: const publishButton = page.locator( '.editor-header__settings button.editor-post-publish-button__button' );Or use a more semantic selector like |
||
|
|
||
| // Wait for and click the confirm publish button in the panel | ||
| const confirmPublishButton = page.locator( 'button.editor-post-publish-button__button' ).last(); | ||
|
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. Flaky Test Risk: Using Similar to the previous comment, using const confirmPublishButton = page.locator( '.editor-post-publish-panel button.editor-post-publish-button__button' ); |
||
| await confirmPublishButton.waitFor( { state: 'visible', timeout: 10_000 } ); | ||
| await confirmPublishButton.click(); | ||
|
|
||
| // Wait for success message | ||
| await expect( page.locator( '.components-snackbar' ) ).toBeVisible( { timeout: 10_000 } ); | ||
|
|
||
| // Verify post was created by visiting posts list | ||
| await page.goto( getUrlWithAutoLogin( `${ wpAdminUrl }/edit.php` ) ); | ||
|
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. Cross-Platform: URL construction Same as the earlier comment - consider using await page.goto( getUrlWithAutoLogin( new URL( '/wp-admin/edit.php', wpAdminUrl ).toString() ) ); |
||
| await expect( page.locator( 'a.row-title:has-text("E2E Test Post")' ) ).toBeVisible(); | ||
| } ); | ||
|
|
||
| test( 'uploads media', async ( { page } ) => { | ||
| // Navigate to media library | ||
| const addNewMediaUrl = `${ wpAdminUrl }/media-new.php`; | ||
| await page.goto( getUrlWithAutoLogin( addNewMediaUrl ) ); | ||
|
|
||
| // Create a minimal valid PNG file (1x1 red pixel) | ||
| const pngBuffer = Buffer.from( [ | ||
| 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, | ||
| 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, | ||
| 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08, 0x99, 0x63, 0xf8, | ||
| 0xcf, 0xc0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x18, 0xdd, 0x83, 0x75, 0x00, 0x00, 0x00, | ||
| 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, | ||
| ] ); | ||
|
|
||
| const testImagePath = '/tmp/e2e-test-image.png'; | ||
| await fs.writeFile( testImagePath, pngBuffer ); | ||
|
|
||
| // Upload the file using the HTML5 plupload file input | ||
| // The actual file input is hidden, so we need to set files directly on it | ||
| const fileInputPromise = page.waitForEvent( 'filechooser' ); | ||
| await page.locator( '#plupload-browse-button' ).click(); | ||
| const fileChooser = await fileInputPromise; | ||
| await fileChooser.setFiles( testImagePath ); | ||
|
|
||
| // Wait for upload to complete | ||
| await expect( page.locator( '.media-item .filename' ) ).toBeVisible( { timeout: 30_000 } ); | ||
|
|
||
| // Clean up test file | ||
| await fs.unlink( testImagePath ); | ||
|
|
||
| // Verify media was uploaded by checking the library | ||
| await page.goto( getUrlWithAutoLogin( `${ wpAdminUrl }/upload.php` ) ); | ||
| const mediaItems = page.locator( '.attachment' ); | ||
| await expect( mediaItems.first() ).toBeVisible(); | ||
| } ); | ||
|
|
||
| test( 'activates themes', async ( { page } ) => { | ||
| // Navigate to themes page | ||
| const themesUrl = `${ wpAdminUrl }/themes.php`; | ||
| await page.goto( getUrlWithAutoLogin( themesUrl ) ); | ||
|
|
||
| // Find a non-active theme | ||
| const inactiveTheme = page.locator( '.theme:not(.active)' ).first(); | ||
| await expect( inactiveTheme ).toBeVisible(); | ||
|
|
||
| // Get the theme slug before activating | ||
| const themeSlug = await inactiveTheme.getAttribute( 'data-slug' ); | ||
| expect( themeSlug ).toBeTruthy(); | ||
|
|
||
| // Hover and click activate | ||
| await inactiveTheme.click(); | ||
| const activateButton = page.locator( '.inactive-theme > .button.activate' ).first(); | ||
| await activateButton.click(); | ||
|
|
||
| // Wait for page reload | ||
| await page.waitForLoadState( 'networkidle' ); | ||
|
|
||
| // Verify the theme is now active | ||
| const activeTheme = page.locator( `.theme.active[data-slug="${ themeSlug }"]` ); | ||
| await expect( activeTheme ).toBeVisible(); | ||
| } ); | ||
|
|
||
| test( 'adds new themes', async ( { page } ) => { | ||
| // Navigate to themes page | ||
| const themeInstallUrl = `${ wpAdminUrl }/theme-install.php`; | ||
| await page.goto( getUrlWithAutoLogin( themeInstallUrl ) ); | ||
|
|
||
| // Search for a theme | ||
| const searchInput = page.locator( '#wp-filter-search-input' ); | ||
| await searchInput.fill( 'Twenty Twenty-Two' ); | ||
| await searchInput.press( 'Enter' ); | ||
|
|
||
| // Wait for search results | ||
| await page.waitForLoadState( 'networkidle' ); | ||
|
|
||
| // Find Twenty Twenty-Two theme | ||
| const themeResult = page.locator( '.theme[data-slug="twentytwentytwo"]' ).first(); | ||
|
|
||
| // Install the theme | ||
| await themeResult.click(); | ||
| const themeInstallOverlay = page.locator( '.theme-install-overlay' ); | ||
| await expect( themeInstallOverlay ).toBeVisible( { timeout: 5_000 } ); | ||
| const installButton = themeInstallOverlay.locator( 'a.theme-install' ); | ||
| await installButton.click(); | ||
|
|
||
| // Wait for installation to complete | ||
| await expect( themeInstallOverlay.locator( 'a.activate' ) ).toBeVisible( { | ||
| timeout: 30_000, | ||
| } ); | ||
| } ); | ||
|
|
||
| test( 'activates plugin', async ( { page } ) => { | ||
| // Navigate to plugins page | ||
| const pluginsUrl = `${ wpAdminUrl }/plugins.php`; | ||
| await page.goto( getUrlWithAutoLogin( pluginsUrl ) ); | ||
|
|
||
| // Find an inactive plugin (Hello Dolly is usually installed by default) | ||
| const inactivePlugin = page.locator( 'tr.inactive[data-slug="hello-dolly"]' ).first(); | ||
| await expect( inactivePlugin ).toBeVisible(); | ||
| const pluginSlug = await inactivePlugin.getAttribute( 'data-slug' ); | ||
|
|
||
| // Activate the plugin | ||
| const activateLink = inactivePlugin.locator( 'span.activate a' ); | ||
| await activateLink.click(); | ||
|
|
||
| // Wait for plugin to be activated | ||
| await page.waitForLoadState( 'networkidle' ); | ||
|
|
||
| // Verify plugin is now active | ||
| const activePlugin = page.locator( `tr.active[data-slug="${ pluginSlug }"]` ); | ||
| await expect( activePlugin ).toBeVisible(); | ||
| } ); | ||
|
|
||
| test( 'adds new plugin', async ( { page } ) => { | ||
| // Navigate to plugins page | ||
| const pluginInstallUrl = `${ wpAdminUrl }/plugin-install.php`; | ||
| await page.goto( getUrlWithAutoLogin( pluginInstallUrl ) ); | ||
|
|
||
| // Search for a plugin | ||
| const searchInput = page.locator( '#search-plugins' ); | ||
| await searchInput.fill( 'Contact Form 7' ); | ||
| await searchInput.press( 'Enter' ); | ||
|
|
||
| // Wait for search results | ||
| await page.waitForLoadState( 'networkidle' ); | ||
|
|
||
| // Find Contact Form 7 plugin | ||
| const pluginResult = page.locator( '.plugin-card-contact-form-7' ).first(); | ||
| await expect( pluginResult ).toBeVisible(); | ||
|
|
||
| // Install the plugin | ||
| const installButton = pluginResult.locator( 'a.install-now' ); | ||
| await installButton.click(); | ||
|
|
||
| // Wait for installation to complete | ||
| await page.waitForLoadState( 'networkidle' ); | ||
|
|
||
| // Verify plugin was installed | ||
| await expect( pluginResult.locator( 'a.activate-now' ) ).toBeVisible( { | ||
| timeout: 30_000, | ||
| } ); | ||
| } ); | ||
|
|
||
| test( '"Post name" permalink structure works', async ( { page } ) => { | ||
| // Navigate to permalink settings | ||
| const permalinkUrl = `${ wpAdminUrl }/options-permalink.php`; | ||
| await page.goto( getUrlWithAutoLogin( permalinkUrl ) ); | ||
|
|
||
| // Select "Post name" permalink structure | ||
| const postNameRadio = page.locator( 'input#permalink-input-post-name' ); | ||
| await postNameRadio.check(); | ||
|
|
||
| // Save changes | ||
| const saveButton = page.locator( '#submit' ); | ||
| await saveButton.click(); | ||
|
|
||
| // Wait for success message | ||
| await expect( page.locator( '#setting-error-settings_updated' ) ).toBeVisible(); | ||
|
|
||
| // Verify the setting was saved | ||
| await expect( postNameRadio ).toBeChecked(); | ||
|
|
||
| // Create a test post to verify the permalink structure works | ||
| const newPostUrl = `${ wpAdminUrl }/post-new.php`; | ||
| await page.goto( getUrlWithAutoLogin( newPostUrl ) ); | ||
|
|
||
| const editorFrame = page.frameLocator( 'iframe[name="editor-canvas"]' ); | ||
|
|
||
| // Close welcome guide if it appears (always on main page, not in iframe) | ||
| await closeWelcomeGuide( page ); | ||
|
|
||
| // Wait for title to be available in iframe | ||
| const titleSelector = 'h1.editor-post-title'; | ||
| await editorFrame.locator( titleSelector ).waitFor( { timeout: 30_000 } ); | ||
| await editorFrame.locator( titleSelector ).fill( 'Permalink Test Post' ); | ||
|
|
||
| // Click into the content area and type | ||
| await editorFrame.locator( titleSelector ).press( 'Enter' ); | ||
| const contentBlock = editorFrame.locator( 'p[role="document"]' ).first(); | ||
| await contentBlock.fill( 'Testing permalink structure.' ); | ||
|
|
||
| // Publish the post (publish buttons are on main page) | ||
| const publishButton = page.locator( 'button.editor-post-publish-button__button' ).first(); | ||
| await publishButton.waitFor( { state: 'visible', timeout: 10_000 } ); | ||
| await publishButton.click(); | ||
|
|
||
| // Wait for and click the confirm publish button in the panel | ||
| const confirmPublishButton = page.locator( 'button.editor-post-publish-button__button' ).last(); | ||
| await confirmPublishButton.waitFor( { state: 'visible', timeout: 10_000 } ); | ||
| await confirmPublishButton.click(); | ||
|
|
||
| // Wait for publish | ||
| await expect( page.locator( '.components-snackbar' ) ).toBeVisible( { timeout: 10_000 } ); | ||
|
|
||
| // Get the post URL from the editor | ||
| const viewPostLink = page.locator( 'a:has-text("View Post")' ).first(); | ||
| const postUrl = await viewPostLink.getAttribute( 'href' ); | ||
|
|
||
| // Verify the URL follows the post name structure (contains the post slug) | ||
| expect( postUrl ).toMatch( /\/permalink-test-post\/?$/ ); | ||
|
|
||
| // Visit the post and verify it loads | ||
| await page.goto( postUrl! ); | ||
| await page.waitForLoadState( 'networkidle' ); | ||
| await expect( page.locator( 'h1' ) ).toHaveText( 'Permalink Test Post' ); | ||
| await expect( page.locator( 'div.entry-content' ) ).toHaveText( | ||
| 'Testing permalink structure.' | ||
| ); | ||
| } ); | ||
| } ); | ||
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.
Bug: Loop counter mismatch
The comment on line 10 states "Attempts to close up to 3 times" but the loop condition is
i < 2, which only allows 2 iterations (i=0 and i=1).Either update the loop to
i < 3or update the comment to say "up to 2 times".