Skip to content

Conversation

@gcsecsey
Copy link
Contributor

Related issues

  • Fixes #

Proposed Changes

Testing Instructions

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@claude
Copy link

claude bot commented Nov 11, 2025

Claude finished @gcsecsey's task —— View job


Code Review: Add navigation smoke tests

I've completed a comprehensive review of the new E2E test file. Overall, this is a solid addition to the test suite with good coverage of basic navigation flows. Below is my detailed feedback.


Summary

Strengths:

  • ✅ Good test coverage of critical user journeys (homepage, wp-admin login, post creation)
  • ✅ Consistent with existing E2E test patterns in the codebase
  • ✅ Uses page objects appropriately for maintainability
  • ✅ Proper test isolation with beforeAll/afterAll hooks
  • ✅ Cross-platform compatible (no platform-specific code paths)

Issues Found:

  • 🐛 Bug: Loop counter mismatch (line 15)
  • ⚠️ Flaky Test Risks: Fragile selectors using .first() and .last() (lines 122-127)
  • 🔧 Minor improvements needed for URL construction and error handling

Detailed Findings

1. Cross-Platform Compatibility ✅

The test file is cross-platform compatible:

  • No hardcoded path separators (URLs are constructed, not filesystem paths)
  • No platform-specific assumptions
  • Uses Playwright's cross-platform APIs
  • Works with the existing E2ESession which handles platform differences

Minor suggestions: Use new URL() for URL construction instead of string concatenation (see inline comments on lines 102, 135)

2. Code Quality Issues

Bug: Loop iteration mismatch (line 15)

The comment says "up to 3 times" but the loop only runs twice. See inline comment for fix.

Fragile selectors (lines 122-127)

Using .first() and .last() on button selectors is risky - WordPress could change button order or add new buttons. Consider more specific selectors scoped to the relevant container (editor header, publish panel). See inline comments.

Silent error swallowing (line 25-29)

The empty catch block makes debugging harder. Consider at least logging the error or adding a comment explaining why it's expected.

3. Security ✅

No security concerns:

  • Uses getUrlWithAutoLogin() helper consistently for authenticated access
  • No hardcoded credentials or secrets
  • No direct HTML manipulation or injection risks
  • Proper URL encoding in utility functions

4. Performance ⚡

  • 30-second timeout on line 112 seems high but may be necessary for CI environments - consider adding a comment explaining why
  • Test uses reasonable timeouts overall (2s, 10s, 30s, 120s where appropriate)
  • Sequential test execution enforced by Playwright config (workers: 1) due to app single-instance limitation

5. Testing Quality

Test Coverage: Good smoke test coverage:

  • ✅ Homepage loads with WordPress indicators
  • ✅ WP-Admin auto-login works
  • ✅ Post creation flow (Block Editor)

Potential improvements:

  • Consider testing navigation between created post and frontend to verify it appears
  • The post creation test doesn't clean up afterward (see inline comment on lines 100-137) - this may be fine for ephemeral test sites, but worth documenting
  • Could add test for error states (e.g., site not running)

Test Quality:

  • Good use of explicit waits with timeouts
  • Proper use of Playwright's iframe handling
  • Appropriate assertions (visibility, URL matching, text content)

6. Documentation

Missing:

  • PR description is empty (template not filled out)
  • No issue reference (Fixes #)
  • No testing instructions
  • Consider adding JSDoc comments to closeWelcomeGuide() explaining why it tries multiple times

Docs/ai-instructions.md: No updates needed - this is a straightforward E2E test addition that doesn't change architecture or introduce new patterns.


Test Patterns Consistency ✅

Compared to sites.test.ts, this file follows the same patterns:

  • ✅ Uses E2ESession for app lifecycle
  • ✅ Completes onboarding in beforeAll
  • ✅ Handles WhatsNewModal
  • ✅ Uses page objects (SiteContent, etc.)
  • ✅ Uses getUrlWithAutoLogin() for authenticated access
  • ✅ Appropriate timeouts (120s for site ready, etc.)

Recommendations

Must Fix (blocks merge):

  1. Fix the loop counter bug on line 15

Should Fix (before merge):
2. Improve button selectors to be less fragile (lines 122, 127)
3. Fill out the PR template (description, related issue, testing instructions)

Nice to Have (can be follow-up):
4. Use new URL() for URL construction
5. Add comment explaining 30s timeout
6. Consider adding test cleanup or unique post titles
7. Add debug logging to catch blocks


Pre-merge Checklist Items

Based on the PR template:

  • ⚠️ TypeScript/React errors: No TypeScript errors in the test file itself, but please verify the test runs successfully with npm run e2e
  • ⚠️ Testing instructions: Please add testing instructions to the PR description
  • ⚠️ Related issue: Please link the related issue

Great work on adding these smoke tests! The core logic is solid and follows good patterns. Please address the bug on line 15 and consider the selector improvements to reduce test flakiness. Let me know if you have questions about any of the feedback.

*/
async function closeWelcomeGuide( page: Page ) {
// Try to close the modal up to 3 times
for ( let i = 0; i < 2; i++ ) {
Copy link

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).

Suggested change
for ( let i = 0; i < 2; i++ ) {
for ( let i = 0; i < 3; i++ ) {

Either update the loop to i < 3 or update the comment to say "up to 2 times".

Comment on lines +25 to +29
} catch ( e ) {
// Modal not found or already closed, exit the loop
break;
}
}
Copy link

Choose a reason for hiding this comment

The 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.

Comment on lines +100 to +137
test( 'creates a post', async ( { page } ) => {
// Navigate to new post page
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( '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.click();
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();

// 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 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` ) );
await expect( page.locator( 'a.row-title:has-text("E2E Test Post")' ) ).toBeVisible();
} );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Coverage: Missing cleanup

This test creates a post but doesn't clean it up afterward. Consider one of these approaches:

  1. Add cleanup: Delete the post after verification
  2. Use a unique identifier: Add timestamp/UUID to post title to avoid conflicts in repeated runs
  3. Document the behavior: Add a comment explaining that test posts accumulate

Since this is an E2E test and the entire site is ephemeral (created per test session), this may be acceptable, but it's worth making the decision explicit.


test( 'creates a post', async ( { page } ) => {
// Navigate to new post page
const newPostUrl = `${ wpAdminUrl }/post-new.php`;
Copy link

Choose a reason for hiding this comment

The 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 new URL() for consistency and to avoid potential issues:

		const newPostUrl = new URL( '/wp-admin/post-new.php', wpAdminUrl ).toString();

This ensures proper URL handling even if wpAdminUrl has trailing slashes or other edge cases.


// Wait for title to be available in iframe
const titleSelector = 'h1.editor-post-title';
await editorFrame.locator( titleSelector ).waitFor( { timeout: 30_000 } );
Copy link

Choose a reason for hiding this comment

The 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:

  1. Reducing to 15-20 seconds if this typically loads faster
  2. Adding a comment explaining why 30s is needed (e.g., slow CI environments)
		// Wait for title to be available in iframe (long timeout for slower CI environments)
		await editorFrame.locator( titleSelector ).waitFor( { timeout: 30_000 } );

Comment on lines +122 to +124
const publishButton = page.locator( 'button.editor-post-publish-button__button' ).first();
await publishButton.waitFor( { state: 'visible', timeout: 10_000 } );
await publishButton.click();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky Test Risk: Selector fragility

Using .first() on a selector that matches multiple buttons is fragile. If WordPress changes the order or adds more buttons, this could break or click the wrong button.

Consider more specific selectors:

		const publishButton = page.locator( '.editor-header__settings button.editor-post-publish-button__button' );

Or use a more semantic selector like getByRole('button', { name: /publish/i }) if the button text is stable.

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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky Test Risk: Using .last() is fragile

Similar to the previous comment, using .last() is brittle. The "confirm publish" button should have a more specific selector or be located within the publish panel:

		const confirmPublishButton = page.locator( '.editor-post-publish-panel button.editor-post-publish-button__button' );

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` ) );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-Platform: URL construction

Same as the earlier comment - consider using new URL():

		await page.goto( getUrlWithAutoLogin( new URL( '/wp-admin/edit.php', wpAdminUrl ).toString() ) );

@github-actions
Copy link

📊 Performance Test Results

Comparing d723766 vs trunk

site-editor

Metric trunk d723766 Diff Change
load 12787.00 ms 19192.00 ms +6405.00 ms 🔴 50.1%

site-startup

Metric trunk d723766 Diff Change
siteCreation 21050.00 ms 19155.00 ms -1895.00 ms 🟢 -9.0%
siteStartup 8018.00 ms 10040.00 ms +2022.00 ms 🔴 25.2%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants