Compare commits
1 Commits
main
...
claude/e2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a859e6d1a |
@@ -7,6 +7,11 @@
|
||||
*
|
||||
* The saved auth state (playwright/.auth/admin.json) is reused by the
|
||||
* "chromium" project so individual tests don't each incur a login round-trip.
|
||||
*
|
||||
* Retry hardening (2026-06-12): Hostinger rate-limits GitHub runner IPs on
|
||||
* wp-login.php, causing TimeoutError on page.goto. The login attempt is now
|
||||
* retried up to MAX_ATTEMPTS times with a fresh browser context per attempt
|
||||
* and BACKOFF_MS delay between attempts so transient throttle windows clear.
|
||||
*/
|
||||
|
||||
import { test as setup, expect } from '@playwright/test';
|
||||
@@ -15,9 +20,14 @@ import * as fs from 'fs';
|
||||
|
||||
const AUTH_FILE = path.join(__dirname, 'playwright/.auth/admin.json');
|
||||
|
||||
setup('authenticate as WP admin', async ({ page }) => {
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const BACKOFF_MS = 15_000; // 15 s between attempts
|
||||
const NAV_TIMEOUT = 90_000; // raised from 30 s → 90 s per attempt
|
||||
|
||||
setup('authenticate as WP admin', async ({ browser }) => {
|
||||
const user = process.env.WP_ADMIN_USER;
|
||||
const password = process.env.WP_ADMIN_PASSWORD;
|
||||
const baseURL = process.env.BASE_URL ?? 'https://staging.peptiderepo.com';
|
||||
|
||||
if (!user || !password) {
|
||||
throw new Error(
|
||||
@@ -25,28 +35,69 @@ setup('authenticate as WP admin', async ({ page }) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
// Ensure auth directory exists
|
||||
fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true });
|
||||
|
||||
await page.goto('/wp-login.php');
|
||||
await page.fill('#user_login', user);
|
||||
await page.fill('#user_pass', password);
|
||||
await page.click('#wp-submit');
|
||||
let lastError: unknown;
|
||||
|
||||
// Wait for any post-login navigation. The site may redirect successful logins
|
||||
// to the homepage (via a login_redirect filter) rather than to wp-admin —
|
||||
// staging does this. So we don't assert the post-submit URL; instead, we
|
||||
// verify we're no longer ON wp-login.php (which would indicate a credential
|
||||
// rejection or 2FA prompt) and then navigate explicitly to wp-admin.
|
||||
await page.waitForURL((url) => !url.pathname.includes('/wp-login.php'), {
|
||||
timeout: 20_000,
|
||||
});
|
||||
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
console.log(
|
||||
`[setup] Login attempt ${attempt}/${MAX_ATTEMPTS} — navigating to wp-login.php ...`
|
||||
);
|
||||
|
||||
// Navigate explicitly into wp-admin to capture the authenticated admin session.
|
||||
await page.goto('/wp-admin/');
|
||||
await expect(page).toHaveURL(/\/wp-admin\//, { timeout: 20_000 });
|
||||
await expect(page.locator('#wpadminbar')).toBeVisible();
|
||||
// Fresh context per attempt so stale cookies / throttle state don't carry over.
|
||||
const context = await browser.newContext({ baseURL });
|
||||
const page = await context.newPage();
|
||||
|
||||
// Persist session cookies + localStorage to disk
|
||||
await page.context().storageState({ path: AUTH_FILE });
|
||||
try {
|
||||
await page.goto('/wp-login.php', { timeout: NAV_TIMEOUT });
|
||||
await page.fill('#user_login', user);
|
||||
await page.fill('#user_pass', password);
|
||||
await page.click('#wp-submit');
|
||||
|
||||
// Wait for any post-login navigation. The site may redirect successful
|
||||
// logins to the homepage (via a login_redirect filter) rather than to
|
||||
// wp-admin — staging does this. So we don't assert the post-submit URL;
|
||||
// instead, we verify we're no longer ON wp-login.php (which would
|
||||
// indicate a credential rejection or 2FA prompt) and then navigate
|
||||
// explicitly to wp-admin.
|
||||
await page.waitForURL(
|
||||
(url) => !url.pathname.includes('/wp-login.php'),
|
||||
{ timeout: NAV_TIMEOUT }
|
||||
);
|
||||
|
||||
// Navigate explicitly into wp-admin to capture the authenticated admin session.
|
||||
await page.goto('/wp-admin/', { timeout: NAV_TIMEOUT });
|
||||
await expect(page).toHaveURL(/\/wp-admin\//, { timeout: 20_000 });
|
||||
await expect(page.locator('#wpadminbar')).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
// Persist session cookies + localStorage to disk
|
||||
await page.context().storageState({ path: AUTH_FILE });
|
||||
|
||||
console.log(`[setup] Login succeeded on attempt ${attempt}/${MAX_ATTEMPTS}`);
|
||||
await context.close();
|
||||
return; // success — exit setup
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error(
|
||||
`[setup] Login attempt ${attempt}/${MAX_ATTEMPTS} FAILED: ${message}`
|
||||
);
|
||||
|
||||
await context.close();
|
||||
|
||||
if (attempt < MAX_ATTEMPTS) {
|
||||
console.log(`[setup] Waiting ${BACKOFF_MS / 1000}s before retry ...`);
|
||||
await new Promise((resolve) => setTimeout(resolve, BACKOFF_MS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All attempts exhausted — surface the last error with a clear diagnostic.
|
||||
throw new Error(
|
||||
`[setup] All ${MAX_ATTEMPTS} login attempts failed. ` +
|
||||
`Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}\n` +
|
||||
`Hint: Hostinger may still be throttling GitHub runner IPs. ` +
|
||||
`Re-run the workflow once the throttle window clears, or increase BACKOFF_MS / MAX_ATTEMPTS.`
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user