commit d46a1e468007732e5ea23910ea3078676d02dd26 Author: peptiderepo Date: Sun Apr 26 04:39:13 2026 +0000 feat: initial Playwright smoke suite 20 tests across 5 files covering all plugin surfaces on staging: - Homepage load and PHP fatal detection - Reconstitution Calculator shortcode + math - Peptide Search AI widget + AJAX response - Admin: all plugins active, PSA + PRAutoBlogger settings pages - REST API: prc/v1/presets, peptides/v1/search, compounds, CPT types Staging shortcode pages: /reconstitution-calculator/ - [prc_calculator] /search-peptides/ - [peptide_search] /peptide-directory/ - [peptide_directory] GHA workflow: smoke.yml supports manual dispatch + workflow_call for use as a deploy gate from plugin repos. diff --git a/.env.example b/.env.example new file mode 100755 index 0000000..409c93a --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Copy this to .env and fill in values — never commit .env to git +BASE_URL=https://staging.peptiderepo.com +WP_ADMIN_USER=peptiderepo +WP_ADMIN_PASSWORD=your-staging-password-here + +# Page paths where shortcodes live (adjust to match actual WP page slugs) +CALC_PAGE_PATH=/reconstitution-calculator/ +SEARCH_PAGE_PATH=/search-peptides/ +DIRECTORY_PAGE_PATH=/peptide-directory/ diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml new file mode 100755 index 0000000..0762721 --- /dev/null +++ b/.github/workflows/smoke.yml @@ -0,0 +1,83 @@ +# smoke.yml +# +# Playwright smoke tests against staging.peptiderepo.com +# +# Triggers: +# - Manually via workflow_dispatch (run before any prod deploy) +# - Called by other repos via workflow_call (deploy gates) +# +# Secrets required (set in GitHub repo Settings → Secrets → Actions): +# STAGING_WP_ADMIN_USER — WordPress admin username on staging +# STAGING_WP_ADMIN_PASSWORD — WordPress admin password on staging + +name: Smoke Tests (Staging) + +on: + workflow_dispatch: + inputs: + base_url: + description: 'Target URL (defaults to staging)' + required: false + default: 'https://staging.peptiderepo.com' + + # Allow other repos to call this workflow as a deploy gate + workflow_call: + inputs: + base_url: + type: string + required: false + default: 'https://staging.peptiderepo.com' + secrets: + STAGING_WP_ADMIN_USER: + required: true + STAGING_WP_ADMIN_PASSWORD: + required: true + +jobs: + smoke: + name: Playwright smoke suite + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: e2e/package-lock.json + + - name: Install dependencies + working-directory: e2e + run: npm ci + + - name: Install Playwright browsers + working-directory: e2e + run: npx playwright install chromium --with-deps + + - name: Run smoke tests + working-directory: e2e + env: + BASE_URL: ${{ inputs.base_url || 'https://staging.peptiderepo.com' }} + WP_ADMIN_USER: ${{ secrets.STAGING_WP_ADMIN_USER }} + WP_ADMIN_PASSWORD: ${{ secrets.STAGING_WP_ADMIN_PASSWORD }} + run: npx playwright test + + - name: Upload test report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: e2e/playwright-report/ + retention-days: 14 + + - name: Upload failure screenshots/videos + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-failures + path: e2e/test-results/ + retention-days: 7 diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..50ec020 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +playwright-report/ +playwright/.auth/ +test-results/ +.env diff --git a/global.setup.ts b/global.setup.ts new file mode 100755 index 0000000..a54521c --- /dev/null +++ b/global.setup.ts @@ -0,0 +1,42 @@ +/** + * global.setup.ts + * + * What: Authenticates to WordPress admin once and persists the session to disk. + * Who triggers it: Playwright "setup" project, runs before all smoke tests. + * Dependencies: WP_ADMIN_USER and WP_ADMIN_PASSWORD env vars. + * + * 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. + */ + +import { test as setup, expect } from '@playwright/test'; +import * as path from 'path'; +import * as fs from 'fs'; + +const AUTH_FILE = path.join(__dirname, 'playwright/.auth/admin.json'); + +setup('authenticate as WP admin', async ({ page }) => { + const user = process.env.WP_ADMIN_USER; + const password = process.env.WP_ADMIN_PASSWORD; + + if (!user || !password) { + throw new Error( + 'WP_ADMIN_USER and WP_ADMIN_PASSWORD must be set in .env or CI secrets' + ); + } + + // Ensure 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'); + + // Wait for successful redirect to wp-admin dashboard + await expect(page).toHaveURL(/wp-admin/, { timeout: 20_000 }); + await expect(page.locator('#wpadminbar')).toBeVisible(); + + // Persist session cookies + localStorage to disk + await page.context().storageState({ path: AUTH_FILE }); +}); diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 0000000..2b13067 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,92 @@ +{ + "name": "peptide-e2e", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "peptide-e2e", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.43.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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..0a3e141 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "peptide-e2e", + "version": "1.0.0", + "description": "Playwright smoke tests for peptiderepo.com (staging + production)", + "private": true, + "scripts": { + "test": "playwright test", + "test:staging": "BASE_URL=https://staging.peptiderepo.com playwright test", + "test:prod": "BASE_URL=https://peptiderepo.com playwright test", + "test:headed": "playwright test --headed", + "report": "playwright show-report" + }, + "devDependencies": { + "@playwright/test": "^1.43.0", + "dotenv": "^16.4.5" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100755 index 0000000..b2e2444 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,61 @@ +/** + * Playwright configuration for peptiderepo.com smoke tests. + * + * Usage: + * npm test — run against BASE_URL (defaults to staging) + * npm run test:staging — explicit staging run + * npm run test:prod — production smoke run + * + * Environment variables (set in .env or CI secrets): + * BASE_URL — target site root (default: https://staging.peptiderepo.com) + * WP_ADMIN_USER — WordPress admin username + * WP_ADMIN_PASSWORD — WordPress admin password + * CALC_PAGE_PATH — URL path to the reconstitution calculator page (default: /reconstitution-calculator/) + * SEARCH_PAGE_PATH — URL path to the peptide search page (default: /) + * DIRECTORY_PAGE_PATH — URL path to the peptide directory page (default: /peptides/) + */ + +import { defineConfig, devices } from '@playwright/test'; +import * as dotenv from 'dotenv'; +import * as path from 'path'; + +dotenv.config({ path: path.resolve(__dirname, '.env') }); + +const BASE_URL = process.env.BASE_URL ?? 'https://staging.peptiderepo.com'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: false, // WP staging is single-server; parallel can cause rate-limit hits + forbidOnly: !!process.env.CI, // Prevent .only from landing in CI + retries: process.env.CI ? 1 : 0, + workers: 1, // Sequential against staging to be polite + reporter: process.env.CI + ? [['github'], ['html', { open: 'never' }]] + : [['list'], ['html', { open: 'on-failure' }]], + + use: { + baseURL: BASE_URL, + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + actionTimeout: 15_000, + navigationTimeout: 30_000, + }, + + projects: [ + // Auth setup — runs first, saves session to disk + { + name: 'setup', + testMatch: /global\.setup\.ts/, + }, + // All smoke tests — depend on auth setup + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/admin.json', + }, + dependencies: ['setup'], + }, + ], +}); diff --git a/tests/00-homepage.spec.ts b/tests/00-homepage.spec.ts new file mode 100755 index 0000000..6d82fea --- /dev/null +++ b/tests/00-homepage.spec.ts @@ -0,0 +1,40 @@ +/** + * 00-homepage.spec.ts + * + * What: Verifies the site root loads with no fatal PHP errors and basic + * theme elements are present. Catches theme activation failures, + * white screens, and missing plugin-provided content. + * Who triggers it: Playwright chromium project (post-auth setup). + * Dependencies: peptide-starter-theme, PR Core active. + */ + +import { test, expect } from '@playwright/test'; + +test.describe('Homepage', () => { + test('loads with HTTP 200 and no fatal error', async ({ page }) => { + const response = await page.goto('/'); + expect(response?.status()).toBe(200); + }); + + test('has no PHP fatal error or white screen', async ({ page }) => { + await page.goto('/'); + const body = await page.textContent('body'); + // PHP fatals produce these strings in non-production WP configs + expect(body).not.toContain('Fatal error'); + expect(body).not.toContain('Parse error'); + expect(body).not.toContain('Warning: '); + // White-screen: body is empty or nearly empty + expect((body ?? '').trim().length).toBeGreaterThan(200); + }); + + test('renders site name in page title', async ({ page }) => { + await page.goto('/'); + await expect(page).toHaveTitle(/peptiderepo/i); + }); + + test('wp-admin bar present (logged-in session)', async ({ page }) => { + await page.goto('/'); + // The auth state from global.setup means we should see the admin bar + await expect(page.locator('#wpadminbar')).toBeVisible(); + }); +}); diff --git a/tests/01-reconstitution-calculator.spec.ts b/tests/01-reconstitution-calculator.spec.ts new file mode 100755 index 0000000..d606a3e --- /dev/null +++ b/tests/01-reconstitution-calculator.spec.ts @@ -0,0 +1,69 @@ +/** + * 01-reconstitution-calculator.spec.ts + * + * What: Smoke-tests the Peptide Reconstitution Calculator (PRC) plugin. + * Verifies the shortcode renders, presets load, and basic math produces + * a non-zero result — catching broken PHP, failed asset enqueue, or + * missing PR Core integration. + * Who triggers it: Playwright chromium project (post-auth setup). + * Dependencies: peptide-reconstitution-calculator plugin active, [prc_calculator] + * shortcode on the page at CALC_PAGE_PATH. + * + * @see C:\Users\ongte\apps\Peptide Repo CTO\app-maps\peptide-reconstitution-calculator.md + */ + +import { test, expect } from '@playwright/test'; + +const CALC_PATH = process.env.CALC_PAGE_PATH ?? '/reconstitution-calculator/'; + +test.describe('Reconstitution Calculator', () => { + test.beforeEach(async ({ page }) => { + const res = await page.goto(CALC_PATH); + // Tolerate 200 or 301 (redirect to trailing slash) + expect([200, 301]).toContain(res?.status()); + await page.waitForLoadState('networkidle'); + }); + + test('shortcode widget renders on page', async ({ page }) => { + // The shortcode wraps the widget in a container with class prc-calculator + // or similar; at minimum a