Skip to content

Commit 6b60249

Browse files
committed
E2E test fixes
1 parent 43dc565 commit 6b60249

24 files changed

Lines changed: 148 additions & 168 deletions

_TODO.md

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,8 @@
11
<!-- markdownlint-disable-file -->
22
# TODO
33

4-
## Visual Regression Testing
5-
6-
Playwright's Built-in Visual Testing
7-
8-
Playwright has a built-in visual comparison feature using await expect(page).toHaveScreenshot().
9-
10-
How it works: On the first run, Playwright saves a baseline screenshot. Subsequent runs compare the actual screenshot to this baseline, failing the test if there are pixel differences.
11-
12-
Pros: It's free, everything stays local (no third-party services needed), setup is simple, and you retain full control over the baseline images in your repository.
13-
14-
Cons: Browser rendering can be inconsistent across different operating systems and machines, leading to "flaky" tests or false positives. Managing baselines for multiple browsers and resolutions manually can also be challenging at scale.
15-
164
## E2E
175

18-
56 …omium] › test/e2e/specs/02-pages/contact.spec.ts:15:3 › Contact Page › @ready hero section displays (5.3s)
19-
20-
70 …test/e2e/specs/02-pages/homepage.spec.ts:25:3 › Homepage › @ready featured services section renders (5.5s)
21-
22-
85 …st/e2e/specs/02-pages/services.spec.ts:21:3 › Services List Page › @ready services section displays (5.3s)
23-
24-
93 [chromium] › test/e2e/specs/02-pages/tags.spec.ts:15:3 › Tags Index Page › @ready tag list displays (6.0s)
25-
26-
100 …nsent-checkbox.spec.ts:89:3 › Newsletter GDPR Consent › @ready privacy policy link opens in new tab (6.6s)
27-
28-
117 …ubscription.spec.ts:28:3 › Newsletter Subscription Form › @ready form rejects invalid email format (373ms)
29-
30-
134 …nts/breadcrumbs.spec.ts:22:3 › Breadcrumbs Component › @ready breadcrumbs display on service pages (305ms)
31-
32-
156 …5:3 › Consent Preferences Component › @containers full stack consent submission hits backend mocks (30.0s)
33-
34-
163 …2e/specs/04-components/footer.spec.ts:30:3 › Footer Component › @ready footer has copyright notice (421ms)
35-
36-
172 …ponents/icon.spec.ts:38:3 › Icon Component › default variant renders every local icon exactly once (30.1s)
37-
38-
204 …/navigation-mobile.spec.ts:232:3 › Mobile Navigation › @ready can navigate to page from mobile menu (6.0s)
39-
40-
219 …ares.spec.ts:46:3 › Social Shares Component › renders social share component inside testing fixture (5.3s)
41-
42-
257 …› Theme Picker Component › System Preferences › @ready respects prefers-color-scheme on first visit (1.2s)
43-
44-
361 …ility/aria-screen-readers.spec.ts:232:3 › ARIA and Screen Readers › @ready page has exactly one h1 (16.7s
45-
46-
397 …/12-performance/lighthouse.spec.ts:9:3 › Lighthouse Performance › run Lighthouse audit on homepage (14.5s)
47-
48-
653 …ares.spec.ts:46:3 › Social Shares Component › renders social share component inside testing fixture (5.5s)
49-
50-
663 …Nanostore Persistence Across Navigation › @ready theme preference persists across View Transitions (30.2s)
51-
52-
795 …ility/aria-screen-readers.spec.ts:232:3 › ARIA and Screen Readers › @ready page has exactly one h1 (10.8s)
53-
546
## Missing E2E Component Tests
557

568
- BugReporter
@@ -60,9 +12,6 @@ Cons: Browser rendering can be inconsistent across different operating systems a
6012
- Consent/Checkbox
6113
- Diagram
6214
- FileExplorer
63-
- Inset
64-
- Time
65-
- Troubleshooter
6615

6716
## Performance
6817

src/components/Consent/Checkbox/layouts/newsletter-cta-home.astro

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,16 @@ const {
4848
<a
4949
href={privacyPolicyUrl}
5050
class="inline-flex min-h-6 items-center align-middle text-content-offset underline decoration-current hover:text-content-active focus-visible:text-content-active"
51+
target="_blank"
52+
rel="noopener noreferrer"
5153
>
5254
Privacy Policy
5355
</a>.{' '}
5456
<a
5557
href={cookiePolicyUrl}
5658
class="inline-flex min-h-6 items-center align-middle text-content-offset underline decoration-current hover:text-content-active focus-visible:text-content-active"
59+
target="_blank"
60+
rel="noopener noreferrer"
5761
>
5862
Cookie Policy
5963
</a>. No spam,

src/components/Pages/Newsletter/Confirm/index.astro

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ const { token } = Astro.props
7171
size={10}
7272
/>
7373
</div>
74-
<h1 class="text-2xl sm:text-3xl font-bold text-page-inverse" id="confirm-heading-success" tabindex="-1">
74+
<h2 class="text-2xl sm:text-3xl font-bold text-page-inverse" id="confirm-heading-success" tabindex="-1">
7575
Subscription Confirmed!
76-
</h1>
76+
</h2>
7777
</div>
7878
<div class="px-6 py-8">
7979
<p class="text-lg text-content-active mb-2 text-center">
@@ -140,9 +140,9 @@ const { token } = Astro.props
140140
size={10}
141141
/>
142142
</div>
143-
<h1 class="text-2xl sm:text-3xl font-bold text-page-inverse" id="confirm-heading-expired" tabindex="-1">
143+
<h2 class="text-2xl sm:text-3xl font-bold text-page-inverse" id="confirm-heading-expired" tabindex="-1">
144144
Confirmation Link Expired
145-
</h1>
145+
</h2>
146146
</div>
147147
<div class="px-6 py-8">
148148
<p class="text-lg text-content-active text-center mb-2">
@@ -192,9 +192,9 @@ const { token } = Astro.props
192192
size={10}
193193
/>
194194
</div>
195-
<h1 class="text-2xl sm:text-3xl font-bold text-danger" id="error-title" tabindex="-1">
195+
<h2 class="text-2xl sm:text-3xl font-bold text-danger" id="error-title" tabindex="-1">
196196
Confirmation Error
197-
</h1>
197+
</h2>
198198
</div>
199199
<div class="px-6 py-8">
200200
<p class="text-lg text-content-active text-center mb-6" id="error-message">

test/e2e/helpers/pageObjectModels/BasePage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -883,13 +883,13 @@ export class BasePage extends BuiltInsPage {
883883
* Verify service card has required elements
884884
*/
885885
async expectServiceCard(): Promise<void> {
886-
const firstCard = this._page.locator('.service-item').first()
886+
const firstCard = this._page.locator('article[data-service-card]').first()
887887

888-
// Each service should have h3 title
889-
await expect(firstCard.locator('h3')).toBeVisible()
888+
// Each service should have a visible title
889+
await expect(firstCard.locator('h2, h3').first()).toBeVisible()
890890

891-
// Should have a link to the service detail page
892-
await expect(firstCard.locator('a')).toBeVisible()
891+
// Should have a contact CTA link for the service
892+
await expect(firstCard.locator('a[data-service-contact-link]').first()).toBeVisible()
893893
}
894894

895895
/**

test/e2e/helpers/pageObjectModels/NewsletterPage.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,45 @@ export class NewsletterPage extends BasePage {
4949
* Check GDPR consent checkbox
5050
*/
5151
async checkGdprConsent(): Promise<void> {
52-
await this.check(this.gdprConsentSelector)
52+
const checkbox = this.page.locator(this.gdprConsentSelector)
53+
const container = this.page.locator('#newsletter-gdpr-consent-container')
54+
const hiddenConsentSelector = '#newsletter-gdpr-consent-hidden'
55+
56+
if (await checkbox.isHidden().catch(() => false)) {
57+
return
58+
}
59+
60+
await this.page.evaluate(selector => {
61+
const consentCheckbox = document.querySelector<HTMLInputElement>(selector)
62+
if (!consentCheckbox || consentCheckbox.disabled) {
63+
return
64+
}
65+
66+
consentCheckbox.checked = true
67+
consentCheckbox.dispatchEvent(new Event('change', { bubbles: true }))
68+
}, this.gdprConsentSelector)
69+
70+
await this.page.waitForFunction(
71+
({ checkboxSelector, containerSelector, hiddenSelector }) => {
72+
const consentCheckbox = document.querySelector<HTMLInputElement>(checkboxSelector)
73+
const consentContainer = document.querySelector<HTMLElement>(containerSelector)
74+
const hiddenConsentInput = document.querySelector<HTMLInputElement>(hiddenSelector)
75+
76+
if (!consentCheckbox) {
77+
return true
78+
}
79+
80+
const containerHidden = !consentContainer || consentContainer.style.display === 'none'
81+
const hiddenConsentEnabled = Boolean(hiddenConsentInput && !hiddenConsentInput.disabled)
82+
return consentCheckbox.checked || consentCheckbox.disabled || containerHidden || hiddenConsentEnabled
83+
},
84+
{
85+
checkboxSelector: this.gdprConsentSelector,
86+
containerSelector: '#newsletter-gdpr-consent-container',
87+
hiddenSelector: hiddenConsentSelector,
88+
},
89+
{ timeout: wait.defaultWait }
90+
)
5391
}
5492

5593
/**

test/e2e/specs/02-pages/contact.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ test.describe('Contact Page', () => {
1515
test('@ready hero section displays', async ({ page: playwrightPage }) => {
1616
const page = await BasePage.init(playwrightPage)
1717
await page.goto('/contact')
18-
await page.expectTextContains('h1', /Let's Build Something Amazing/)
18+
await page.expectTextContains('h1', /Contact Me/)
19+
await page.expectTextVisible(/Connect with Webstack Builders to discuss your infrastructure needs/)
1920
})
2021

2122
test('@ready contact form is visible', async ({ page: playwrightPage }) => {
@@ -36,11 +37,10 @@ test.describe('Contact Page', () => {
3637
test('@ready optional form fields are present', async ({ page: playwrightPage }) => {
3738
const page = await BasePage.init(playwrightPage)
3839
await page.goto('/contact')
39-
// Optional fields: company, phone, project type, budget, timeline
40+
// Optional fields: company, phone, engagement type, timeline
4041
await page.expectElementVisible('#company')
4142
await page.expectElementVisible('#phone')
4243
await page.expectElementVisible('#project_type')
43-
await page.expectElementVisible('#budget')
4444
await page.expectElementVisible('#timeline')
4545
})
4646

@@ -61,25 +61,25 @@ test.describe('Contact Page', () => {
6161
test('@ready submit button is present', async ({ page: playwrightPage }) => {
6262
const page = await BasePage.init(playwrightPage)
6363
await page.goto('/contact')
64-
await page.expectSubmitButton('Send Project Details')
64+
await page.expectSubmitButton('Send Engagement Details')
6565
})
6666

6767
test('@ready form has proper labels and accessibility', async ({ page: playwrightPage }) => {
6868
const page = await BasePage.init(playwrightPage)
6969
await page.goto('/contact')
7070
// Check that required inputs have associated labels
7171
await page.expectLabelFor('name', /Full Name/)
72-
await page.expectLabelFor('email', /Email/)
73-
await page.expectLabelFor('message', /Project Description/)
72+
await page.expectLabelFor('email', /Email Address/)
73+
await page.expectLabelFor('message', /Description/)
7474
})
7575

7676
test('@ready form sections are properly organized', async ({ page: playwrightPage }) => {
7777
const page = await BasePage.init(playwrightPage)
7878
await page.goto('/contact')
7979
// Check for section headings - use h3 selector to avoid matching text in paragraphs
8080
await page.expectHasHeading('Contact Information')
81-
await page.expectHasHeading('Project Details')
82-
await page.expectHasHeading('Project Files')
81+
await page.expectHasHeading('Engagement Details')
82+
await page.expectHasHeading('Supporting Files')
8383
})
8484

8585
test('@ready data retention notice is displayed', async ({ page: playwrightPage }) => {

test/e2e/specs/02-pages/homepage.spec.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@ test.describe('Homepage', () => {
2222
await page.expectHeading()
2323
})
2424

25-
test('@ready featured services section renders', async ({ page: playwrightPage }) => {
25+
test('@ready service overview section renders', async ({ page: playwrightPage }) => {
2626
const page = await BasePage.init(playwrightPage)
2727
await page.goto('/')
2828

29-
// Check for Featured Services section - Carousel renders an h2 with the title
30-
await page.expectHasHeading('Featured Services')
29+
await page.expectHasHeading('Building the Future of Software Development')
30+
await page.expectTextVisible('Platform Engineering')
31+
await page.expectTextVisible('Cloud Architecture')
32+
await page.expectTextVisible('Developer Experience')
3133
})
3234

33-
test('@ready case studies section displays', async ({ page: playwrightPage }) => {
35+
test('@ready backstage showcase section displays', async ({ page: playwrightPage }) => {
3436
const page = await BasePage.init(playwrightPage)
3537
await page.goto('/')
3638

37-
// Check for Success Stories heading - appears twice (section h2 + carousel h2), use first
38-
await page.expectHasHeading('Success Stories')
39+
await page.expectHasHeading('Give your developers superpowers')
40+
await page.expectTextVisible('Backstage IDP Implementation')
3941
})
4042

4143
test('@ready latest articles section renders', async ({ page: playwrightPage }) => {

test/e2e/specs/02-pages/services.spec.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@ test.describe('Services List Page', () => {
2121
test('@ready services section displays', async ({ page: playwrightPage }) => {
2222
const page = await BasePage.init(playwrightPage)
2323
await page.goto('/services')
24-
// Check for "Our Services" h2 heading
25-
await page.expectHasHeading('Our Services')
24+
await page.expectTextContains('h1', /My Services/)
2625
})
2726

2827
test('@ready service list displays', async ({ page: playwrightPage }) => {
2928
const page = await BasePage.init(playwrightPage)
3029
await page.goto('/services')
31-
// Services are in a list with .service-item class
32-
await page.expectElementVisible('.service-item')
30+
await page.expectElementVisible('article[data-service-card]')
3331
})
3432

3533
test('@ready service cards have required elements', async ({ page: playwrightPage }) => {
@@ -41,25 +39,24 @@ test.describe('Services List Page', () => {
4139
test('@ready service links are functional', async ({ page: playwrightPage }) => {
4240
const page = await BasePage.init(playwrightPage)
4341
await page.goto('/services')
44-
await page.expectAttribute('.service-item a', 'href')
42+
await page.expectAttribute('a[data-service-contact-link]', 'href')
4543
})
4644

47-
test('@ready clicking service navigates to detail page', async ({ page: playwrightPage }) => {
45+
test('@ready clicking service navigates to contact page with service context', async ({ page: playwrightPage }) => {
4846
const page = await BasePage.init(playwrightPage)
4947
await page.goto('/services')
50-
const href = await page.getAttribute('.service-item a', 'href')
48+
const href = await page.getAttribute('a[data-service-contact-link]', 'href')
5149

52-
await page.click('.service-item a')
53-
// NOTE: Avoid strict 'networkidle' gating on WebKit/mobile-safari (can hang on long-lived requests).
54-
await page.waitForNetworkIdleBestEffort()
55-
await page.expectUrlContains(href!)
50+
await page.click('a[data-service-contact-link]')
51+
await page.waitForURL(url => `${url.pathname}${url.search}` === href)
52+
await page.expectTextContains('h1', /Contact Me/)
5653
})
5754

5855
test('@ready responsive: mobile view renders correctly', async ({ page: playwrightPage }) => {
5956
const page = await BasePage.init(playwrightPage)
6057
await page.setViewport(375, 667)
6158
await page.goto('/services')
62-
await page.expectElementVisible('.service-item')
59+
await page.expectElementVisible('article[data-service-card]')
6360
})
6461

6562
test('@ready page has no console errors', async ({ page: playwrightPage }) => {

test/e2e/specs/02-pages/tags.spec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ test.describe('Tags Index Page', () => {
1515
test('@ready tag list displays', async ({ page: playwrightPage }) => {
1616
const page = await BasePage.init(playwrightPage)
1717
await page.goto('/tags')
18-
// Tags are shown as h2 headings linking to tag pages
19-
await page.expectElementVisible('h2 a[href^="/tags/"]')
18+
await page.expectElementVisible('a[href^="/tags/"] h2')
2019
})
2120

2221
test('@ready tag counts display', async ({ page: playwrightPage }) => {

test/e2e/specs/03-forms/contact.spec.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import { TEST_CONTACT_DATA, TEST_EMAILS } from '@test/e2e/fixtures/test-data'
1818

1919
const CONTACT_PATH = '/contact'
2020

21-
const contactSubmitActionEndpoint = '/_actions/contact.submit'
21+
const contactSubmitActionEndpoint = '/_actions/'
22+
const validProjectType = 'solutions-services'
23+
const validTimeline = 'asap'
2224

2325
const waitForContactFormHydration = async (page: BasePage) => {
2426
await page.waitForFunction(() => {
@@ -48,15 +50,13 @@ const fillContactFormWithValidData = async (page: BasePage, overrides?: { email?
4850
await page.fill('#email', email)
4951
await page.fill('#company', TEST_CONTACT_DATA.valid.company)
5052
await page.fill('#phone', TEST_CONTACT_DATA.valid.phone)
51-
await page.locator('#project_type').selectOption('website')
52-
await page.locator('#budget').selectOption('10k-25k')
53-
await page.locator('#timeline').selectOption('asap')
53+
await page.locator('#project_type').selectOption(validProjectType)
54+
await page.locator('#timeline').selectOption(validTimeline)
5455
await page.fill('#message', `${TEST_CONTACT_DATA.valid.message} UI flow ${uniqueSuffix}`)
5556
await page.check('#contact-gdpr-consent')
5657

57-
await expect(page.locator('#project_type')).toHaveValue('website')
58-
await expect(page.locator('#budget')).toHaveValue('10k-25k')
59-
await expect(page.locator('#timeline')).toHaveValue('asap')
58+
await expect(page.locator('#project_type')).toHaveValue(validProjectType)
59+
await expect(page.locator('#timeline')).toHaveValue(validTimeline)
6060
await expect(page.locator('#contact-gdpr-consent')).toBeChecked()
6161

6262
return { email }
@@ -85,25 +85,24 @@ test.describe('Contact Form', () => {
8585
await expect(charCount).toHaveText(String(message.length))
8686
})
8787

88-
test('@ready required select fields block submission when empty', async ({ page: playwrightPage }) => {
88+
test('@ready optional select fields allow submission when empty', async ({ page: playwrightPage }) => {
8989
const page = await setupContactPage(playwrightPage)
9090

9191
await fillRequiredFields(page)
92-
await page.locator('#project_type').selectOption('website')
93-
await page.locator('#timeline').selectOption('asap')
9492
await page.check('#contact-gdpr-consent')
9593

9694
const fetchSpy = await spyOnFetchEndpoint(playwrightPage, contactSubmitActionEndpoint)
9795

9896
try {
9997
await page.click('#submitBtn')
98+
await fetchSpy.waitForCall(wait.defaultWait)
10099

101-
await expect(page.locator('#formErrorBanner')).toBeVisible()
102-
await expect(page.locator('#budget + .field-error')).toContainText('This field is required')
100+
await expect(page.locator('#formErrorBanner')).toBeHidden()
101+
await expect(page.locator('#formMessages .message-success')).toBeVisible({ timeout: wait.defaultWait })
103102

104103
const apiCallCount = await fetchSpy.getCallCount()
105-
if (apiCallCount > 0) {
106-
throw new TestError('Contact API was called despite validation errors')
104+
if (apiCallCount === 0) {
105+
throw new TestError('Contact API was not called when only optional select fields were empty')
107106
}
108107
} finally {
109108
await fetchSpy.restore()

0 commit comments

Comments
 (0)