Compare commits

..

3 Commits

Author SHA1 Message Date
peptiderepo
d194e2109f feat: add board Generate Now smoke spec (M4, skip-if-absent) — v1.5.0
Adds 06-board-generate-now.spec.ts for the PRAutoBlogger board Generate
Now control shipping in v0.21.0 (PR #166). Two tests:

1. Board page loads and #prab-generate-now button is present with nonce.
2. Kick-off AJAX (prautoblogger_generate_now) returns valid JSON — no PHP
   fatal, no wp_send_json_error from a missing nonce.

SKIP-IF-ABSENT: when #prab-board or #prab-generate-now is absent (M4 not
yet deployed) both tests skip with loud annotations so this e2e PR can
merge before prautoblogger PR #166 per the M2/M3 merge-order binding.

READ-ONLY: never waits for generation to complete; kick_off() is
idempotent (guards against duplicate cron scheduling).

Agent-Session: phase2-admin-m4-20260614
2026-06-14 14:02:29 +00:00
peptiderepo
7fa91ec975 fix(ci): sync package-lock.json with package.json (unblock smoke npm ci) (#17)
Lockfile-only sync (Playwright 1.60.0 + typescript). Unblocks org-wide deploy smoke gate.

Agent-Session: e2e-lockfile-sync
2026-06-13 13:19:43 +07:00
peptiderepo
65c5de61cb test: dossier edit+rerun smoke (M3, skip-if-absent) — v1.4.0 (#15)
QA: APPROVE (companion verdict in qa-reviews/prautoblogger/2026-06-12-6ed639d.md check 10). Skip-if-absent per M3 binding.

Agent-Session: cto-e2e-merge-20260612
2026-06-12 22:04:20 +07:00
4 changed files with 198 additions and 15 deletions

View File

@@ -2,6 +2,17 @@
All notable changes to the peptide-e2e smoke suite will be documented here.
## [1.5.0] - 2026-06-14
### Added
- **Board Generate Now smoke (M4, skip-if-absent)** (`06-board-generate-now.spec.ts`).
Asserts the PRAutoBlogger board page loads, `#prab-generate-now` button is present
and carries a nonce, and the kick-off AJAX action returns valid JSON (no PHP fatal).
READ-ONLY: never waits for generation to complete; `kick_off()` is idempotent.
SKIPS with a loud annotation when `#prab-board` or `#prab-generate-now` is absent
(v0.21.0 M4 not yet deployed) so the e2e PR can merge before prautoblogger PR #166
per the M2/M3 merge-order binding. 27 tests total (was 25), delta = 2.
## [1.4.0] - 2026-06-12
### Added

44
package-lock.json generated
View File

@@ -1,25 +1,28 @@
{
"name": "peptide-e2e",
"version": "1.0.0",
"version": "1.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "peptide-e2e",
"version": "1.0.0",
"version": "1.4.0",
"dependencies": {
"typescript": "^6.0.3"
},
"devDependencies": {
"@playwright/test": "^1.43.0",
"@playwright/test": "^1.60.0",
"dotenv": "^16.4.5"
}
},
"node_modules/@playwright/test": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
"integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.59.1"
"playwright": "1.60.0"
},
"bin": {
"playwright": "cli.js"
@@ -57,13 +60,13 @@
}
},
"node_modules/playwright": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.59.1"
"playwright-core": "1.60.0"
},
"bin": {
"playwright": "cli.js"
@@ -76,9 +79,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -87,6 +90,19 @@
"engines": {
"node": ">=18"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "peptide-e2e",
"version": "1.4.0",
"version": "1.5.0",
"description": "Playwright smoke tests for peptiderepo.com (staging + production)",
"private": true,
"scripts": {

View File

@@ -0,0 +1,156 @@
/**
* 06-board-generate-now.spec.ts
*
* What: Smoke for the PRAutoBlogger board "Generate Now" control (v0.21.0, M4).
* Asserts that the board page renders the generate-now button and that
* clicking it sends the kick-off AJAX request without a PHP fatal.
* Read-only assertions only — this test NEVER waits for article generation
* to complete (that would make LLM API calls in CI).
*
* SKIP-IF-ABSENT (M4 binding): the generate-now surface ships with v0.21.0
* (prautoblogger PR #166). This spec guards against the board container
* (#prab-board) being absent or the generate-now button (#prab-generate-now)
* missing. When those selectors are absent (v0.21.0 not yet deployed), the
* entire test suite skips with a loud annotation so this PR can merge before
* the plugin PR — matching the M2 merge-order binding.
*
* Selectors sourced from prautoblogger v0.21.0:
* templates/admin/board-page.php: #prab-board, #prab-generate-now
* assets/js/board-generate.js: data-nonce, wp.ajax / admin-ajax.php
*
* Who triggers it: Playwright chromium project (post-auth setup).
* Dependencies: wp-admin authenticated session (from global.setup.ts).
*
* @see prautoblogger PR #166 (v0.21.0) — the surface under test.
*/
import { test, expect } from '@playwright/test';
const ADMIN = '/wp-admin';
const BOARD_URL = `${ADMIN}/admin.php?page=prautoblogger-board`;
test.describe('WP Admin — PRAutoBlogger board Generate Now (M4)', () => {
test('board page loads and generate-now button is present (skip-if-absent)', async ({ page }) => {
// Navigate to the board page.
const res = await page.goto(BOARD_URL);
// Must be HTTP 200 (guards menu-registration regression).
expect(res?.status()).toBe(200);
// wp-admin chrome must be present.
await expect(page.locator('#wpbody')).toBeVisible();
// Body must not contain PHP fatal or wp_die error strings.
const body = await page.textContent('body');
expect(body).not.toContain('Fatal error');
expect(body).not.toContain('Invalid plugin page');
// ── SKIP-IF-ABSENT guard ────────────────────────────────────────────────
// The generate-now surface ships with v0.21.0. If the board container or
// the button is absent, the M4 plugin is not yet deployed — skip loudly.
const boardContainer = page.locator('#prab-board');
const boardPresent = await boardContainer.count() > 0;
if ( ! boardPresent ) {
test.info().annotations.push({
type: 'skip',
description:
'[generate-now smoke] #prab-board absent: v0.21.0 (M4) not yet deployed. ' +
'Skipping generate-now assertions — they will gate once the plugin PR ships.',
});
test.skip();
return;
}
// Board container must be visible.
await expect(boardContainer).toBeVisible({ timeout: 10_000 });
// ── Generate-now button (skip-if-absent) ────────────────────────────────
const generateBtn = page.locator('#prab-generate-now');
const btnPresent = await generateBtn.count() > 0;
if ( ! btnPresent ) {
test.info().annotations.push({
type: 'skip',
description:
'[generate-now smoke] #prab-generate-now absent: M4 button not yet in deployed build. ' +
'Skipping button assertions.',
});
test.skip();
return;
}
// Button must be visible and carry a nonce for the AJAX call.
await expect(generateBtn).toBeVisible({ timeout: 5_000 });
const nonce = await generateBtn.getAttribute('data-nonce');
expect(nonce).toBeTruthy();
console.log(
'[generate-now smoke] Generate Now button found with nonce — M4 surface is deployed.'
);
});
test('generate-now AJAX responds with JSON (no fatal) — skip-if-absent', async ({ page, request }) => {
// Navigate first to establish auth cookies for the request context.
await page.goto(BOARD_URL);
const body = await page.textContent('body');
if ( ! body?.includes('prab-board') ) {
test.info().annotations.push({
type: 'skip',
description: '[generate-now smoke] Board container absent — v0.21.0 not deployed. Skipping.',
});
test.skip();
return;
}
const generateBtn = page.locator('#prab-generate-now');
if ( await generateBtn.count() === 0 ) {
test.info().annotations.push({
type: 'skip',
description: '[generate-now smoke] #prab-generate-now absent — skipping AJAX test.',
});
test.skip();
return;
}
// Read the nonce from the button (set by wp_localize_script).
const nonce = await generateBtn.getAttribute('data-nonce') ?? '';
// Fire the kick-off AJAX action (same as board-generate.js would).
// We use the request context to intercept without actually triggering
// the full pipeline — the action is idempotent (kick_off() guards against
// duplicate scheduling with wp_next_scheduled()).
//
// NOTE: We assert the response is valid JSON with a 'success' or 'data'
// key only. We do NOT check that generation actually runs (LLM calls).
const adminAjax = `${ADMIN}/admin-ajax.php`;
const formData = new URLSearchParams({
action: 'prautoblogger_generate_now',
nonce,
});
const ajaxRes = await request.post(adminAjax, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: formData.toString(),
});
// Must be HTTP 200 (not 403/500).
expect(ajaxRes.status()).toBe(200);
// Response must be valid JSON (no PHP fatal/notice dumped as HTML).
const text = await ajaxRes.text();
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch {
throw new Error(
`[generate-now smoke] AJAX response is not valid JSON — possible PHP fatal.\nRaw: ${text.slice(0, 500)}`
);
}
// WordPress wp_send_json_success / _error always wraps in { success: bool }.
expect(parsed).toHaveProperty('success');
console.log('[generate-now smoke] AJAX response:', JSON.stringify(parsed).slice(0, 200));
});
});