Skip to content

feat: add AI support structure and migrate to main branch#198

Open
mfilip wants to merge 11 commits into
mainfrom
feat/ai-support-structure
Open

feat: add AI support structure and migrate to main branch#198
mfilip wants to merge 11 commits into
mainfrom
feat/ai-support-structure

Conversation

@mfilip

@mfilip mfilip commented May 29, 2026

Copy link
Copy Markdown
Member
  • Add CLAUDE.md with project documentation
  • Add .claude/skills for build, test, release workflows
  • Update all references from master to main branch
  • Configure semantic-release for main branch
  • Update GitHub workflows for main branch

- Add CLAUDE.md with project documentation
- Add .claude/skills for build, test, release workflows
- Update all references from master to main branch
- Configure semantic-release for main branch
- Update GitHub workflows for main branch
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Walkthrough

This PR migrates E2E testing from Cypress to Playwright (configs, scripts, devDependencies, new Playwright specs and shared helpers), updates CI and release workflows to trigger on main and use Node 24 (replacing the CI Cypress step with Playwright and wiring API_KEY), adds CLAUDE.md and SKILL.md documentation pages, updates .gitignore/.env.example/Makefile/README, and adds a registerEvent() fallback called after module install.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main changeset: addition of AI support documentation structure (CLAUDE.md and .claude/skills) combined with comprehensive migration from master to main branch across configuration files and workflows.
Description check ✅ Passed The description is directly related to the changeset, listing the five key changes: CLAUDE.md addition, .claude/skills documentation, master-to-main migration, semantic-release configuration, and GitHub workflow updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@mfilip mfilip requested a review from cblanc May 29, 2026 09:08
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review: AI support structure + branch migration

Thanks for setting up the AI assistant scaffolding. The CLAUDE.md and skill files are a useful addition. A few issues to flag, mostly around the branch migration half of this PR.

🚨 Blocking: branch rename is incomplete

The PR renames every reference from mastermain, but the GitHub default branch is still master (the only remote head visible is origin/master). If this merges as-is, several things break the moment it lands:

  • Release workflow stops triggering. .github/workflows/release.yml now only fires on pushes to main, but releases happen on master. Until the branch is renamed on GitHub, no releases will publish.
  • PR CI no longer runs for PRs targeting master. .github/workflows/ci.yml filters on branches: [main] for pull_request.
  • make update breaks. git merge --ff-only origin/main will fail because that ref doesn't exist.
  • semantic-release will refuse to release from the current branch since branches: [\"main\"] won't match master.

Two safer options:

  1. Rename the default branch on GitHub first, then merge this PR. Coordinate so the rename and merge happen close together.
  2. Allow both during transition: branches: [\"main\", \"master\"] in release.config.js, and add master back to the workflow pull_request branch filters until the rename is done. Then drop master in a follow-up.

I'd recommend option 1 — it's cleaner and the transition window is short.

Code quality / correctness

  • .claude/settings.json keys aren't part of Claude Code's settings schema. projectType, language, and framework aren't recognized config keys — they'll be silently ignored. The actual schema supports permissions, hooks, env, enabledMcpjsonServers, etc. If the intent is to document the project, that belongs in CLAUDE.md (where it already is). If the file isn't doing anything, consider dropping it or replacing it with real settings (e.g. an allowlist for common commands to reduce permission prompts).

  • .gitignore loses its newline-at-EOF. The added block ends with .windsurf/ with no trailing newline, same as the original. Worth fixing while you're touching the file — most linters and editors expect a trailing newline.

  • CLAUDE.md security note is accurate but easy to misread. The line "Use Twig's json_encode() filter for JS data, never raw JSON.parse()" matches the actual codebase pattern (the catalog template at src/catalog/view/theme/default/template/module/ukaddresssearch.twig:5-8 does use |json_encode() to emit object literals). Worth adding why — i.e. the data is interpolated as a JS object literal inside <script>, so the JSON must be escape-safe — otherwise future readers may misinterpret the rule.

  • CLAUDE.md make bundle row is accurate, but consider noting it requires a prior npm run build since bundle zips src/ without rebuilding the JS. The build skill mentions both steps; CLAUDE.md's quick-commands table makes them look independent.

  • Skill front-matter consistency. All three SKILL.md files use only name and description. That's fine, but Claude Code skills also accept allowed-tools and other fields — worth considering for release since it runs npm run semantic-release and modifies git state.

Performance / Security

  • Nothing material — this PR is docs and config only. No runtime code changes.
  • Minor: confirm .claude/plans/ in .gitignore is intentional — that's a Claude Code convention but only relevant if you actually use plan persistence.

Test coverage

  • N/A — config/docs only. The existing Cypress suite is unaffected.

Suggestions

  • Consider splitting this into two PRs: (1) AI scaffolding (low risk, mergeable now), (2) branch migration (coordinate with the GitHub rename). Bundling them couples a low-risk change to a higher-risk one.
  • If you keep them bundled, add a checklist item to the PR description: "☐ Rename default branch on GitHub before/at merge time."

Happy to re-review once the branch question is sorted out.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
.claude/skills/release/SKILL.md (1)

24-24: 💤 Low value

Capitalise brand name.

"github" should be "GitHub" for the proper brand name.

♻️ Proposed fix
-- **git** - Commits release changes
-- **github** - Creates GitHub release with `idealpostcodes.ocmod.zip`
+- **git** - Commits release changes
+- **GitHub** - Creates GitHub release with `idealpostcodes.ocmod.zip`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/release/SKILL.md at line 24, Update the markdown bullet in
SKILL.md that currently reads "**github** - Creates GitHub release with
`idealpostcodes.ocmod.zip`" to use the correct brand capitalization: change the
bolded "github" token to "GitHub" (i.e., "**GitHub** - Creates GitHub release
with `idealpostcodes.ocmod.zip`") so the brand name is capitalised consistently.
CLAUDE.md (1)

19-27: 💤 Low value

Add language specifier to fenced code block.

The code block would benefit from a language specifier for consistency with Markdown best practices. Consider adding text or plaintext.

♻️ Proposed fix
-```
+```text
 lib/           # TypeScript source (compiled by Rollup)
 src/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 19 - 27, The fenced code block in CLAUDE.md showing
the directory tree lacks a language specifier; update that triple-backtick fence
to include a language (e.g., use ```text or ```plaintext) so Markdown renderers
treat it as plain text—edit the fenced block containing "lib/           #
TypeScript source (compiled by Rollup)" through "docker/        # Docker setup
scripts" and add the language token immediately after the opening ``` to apply
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 9: The pull_request trigger in the GitHub Actions workflow currently only
lists 'main', which will skip CI for PRs targeting 'master'; update the
pull_request branches list in .github/workflows/ci.yml to include both 'main'
and 'master' (e.g., add '- master' alongside '- main') so PR CI runs for either
branch during migration, and plan to remove 'master' from that list after
cutover.

In @.github/workflows/release.yml:
- Line 6: Update the GitHub Actions trigger so releases run from both branches
during the rename: in the workflow's push/branches configuration (the entry
currently listing "main"), add "master" alongside "main" so the release job
triggers for either branch until the repo rename is finished; ensure the
branches array includes both "main" and "master" and keep any existing workflow
names and jobs intact.

---

Nitpick comments:
In @.claude/skills/release/SKILL.md:
- Line 24: Update the markdown bullet in SKILL.md that currently reads
"**github** - Creates GitHub release with `idealpostcodes.ocmod.zip`" to use the
correct brand capitalization: change the bolded "github" token to "GitHub"
(i.e., "**GitHub** - Creates GitHub release with `idealpostcodes.ocmod.zip`") so
the brand name is capitalised consistently.

In `@CLAUDE.md`:
- Around line 19-27: The fenced code block in CLAUDE.md showing the directory
tree lacks a language specifier; update that triple-backtick fence to include a
language (e.g., use ```text or ```plaintext) so Markdown renderers treat it as
plain text—edit the fenced block containing "lib/           # TypeScript source
(compiled by Rollup)" through "docker/        # Docker setup scripts" and add
the language token immediately after the opening ``` to apply the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a1d9a873-6c9d-4793-9beb-175e787a8c2d

📥 Commits

Reviewing files that changed from the base of the PR and between f837b13 and 27853af.

📒 Files selected for processing (11)
  • .claude/settings.json
  • .claude/skills/build/SKILL.md
  • .claude/skills/release/SKILL.md
  • .claude/skills/test/SKILL.md
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • CLAUDE.md
  • Makefile
  • README.md
  • release.config.js

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/release.yml
- Update CI workflow to use Node.js 24.x
- Update release workflow to use Node.js 24.x
- Remove .claude/settings.json configuration file
- Clarify bundle command requires build step first
- Improve security documentation for Twig json_encode filter usage
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

Review — feat: add AI support structure and migrate to main branch

Thanks for putting the AI tooling and skills together — the CLAUDE.md, the three skill files, and the .gitignore additions are well-scoped and the conventions section accurately reflects what's in the codebase (verified Opencart\Admin\Controller\Extension\idealpostcodes\Module namespace and the try/catch + $this->log->write(...) pattern in src/admin/controller/module/ukaddresssearch.php).

That said, the master → main migration half of this PR has some sequencing problems that will break CI/releases on merge.

Blocking — branch rename will break CI and releases on merge

The PR base is master (per gh pr view 198), but every workflow trigger now keys on main:

  • .github/workflows/release.yml triggers on push: branches: [main]. After this lands on master, no further commits will fire the release workflow until the GitHub default branch is renamed.
  • release.config.js is now branches: ["main"]. Even if release.yml did run, semantic-release would refuse to publish ("branch master is not in the configured branches").
  • .github/workflows/ci.yml pull_request: branches: [main] — PRs against master won't trigger CI runs from this trigger.
  • README.md points at blob/main/CHANGELOG.md which 404s until the rename happens on GitHub.
  • Makefile update target does git merge --ff-only origin/main — breaks for any contributor still tracking master.

Recommended order of operations:

  1. Rename the default branch on GitHub (mastermain) via Settings → Branches → rename. GitHub will auto-redirect refs and update open PRs.
  2. Then re-target this PR onto main (gh pr edit 198 --base main).
  3. Merge.

If you can't reorder, a safer interim is to keep the workflow triggers listing both branches (branches: [main, master]) and release.config.js branches: ["main", "master"] so the migration is non-breaking either way.

Medium

  • Node.js bump (CI 18.x → 24.x, release 22.x → 24.x). Fine to move to 24 (LTS as of Oct 2025), but dropping 22 entirely loses coverage for an LTS that's still in active maintenance and likely the version users run in prod. Consider node-version: [22.x, 24.x] matrix on CI; release can stay pinned to 24.
  • CLAUDE.md vs .claude/skills/test/SKILL.md command drift. CLAUDE.md lists npm run cypress:open (matches package.json), while SKILL.md lists npm run test:open. Both exist but behave differently — test:open runs npm start (bootstrap) before opening Cypress; cypress:open assumes containers are already up. Worth a one-line note in the skill so the user picks the right one.

Nits

  • .gitignore still ends with No newline at end of file — the diff appends new entries but doesn't terminate the file with a trailing newline. Trivial to fix.
  • .claude/skills/build/SKILL.md could mention that make bundle requires npm run build to have run first (CLAUDE.md notes this; the skill doesn't).
  • CLAUDE.md Conventions → Security note about Twig json_encode() filter is good context, but would be more useful with a pointer to an example template (e.g. src/admin/view/template/...twig:NN) so the convention is grounded in a concrete callsite.
  • CLAUDE.md "Project Structure" could add a one-line prerequisites note (Docker + Node 24) given the Node bump.

Security & Test coverage

  • No security issues introduced — docs/config only.
  • No test changes needed for this PR. However, the CI trigger change means you won't see PR validation runs on subsequent PRs targeting master, which masks the very issue above — another reason to do the GitHub rename first.

Summary

The AI scaffolding looks great and is accurate to the codebase. The branch-rename half needs to be sequenced behind a GitHub-side default-branch rename (or made bi-branch tolerant) before merging, otherwise this PR will silently break releases and CI triggers.

- Replace Cypress with Playwright test framework
- Update test configuration and file structure
- Move tests from cypress/e2e to tests/ directory
- Update CI workflow to use Playwright with chromium browser
- Replace cypress.config.ts with playwright.config.ts
- Update npm scripts for Playwright commands (ui, headed, playwright)
- Add .env.example for API key configuration
- Update documentation and skill files to reflect Playwright usage
- Remove
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review — feat: add AI support structure and migrate to main branch

Thanks for the migration. The Cypress→Playwright port and mastermain cutover are good directions. A few issues worth addressing before merge, grouped by severity.

Bugs

release.config.js:62path should be a string, not an array.
@semantic-release/github documents assets[].path as a string (file path or glob), not an array. The pre-PR config also had it as an array, so this is not a regression, but while you are touching this file it is worth fixing to avoid silently uploading nothing on the next release:

{ path: "idealpostcodes.ocmod.zip", label: "..." }

tests/checkout.spec.ts:21-24page.route handler is dead code, and there is a race on waitForResponse.

await page.route('https://api.ideal-postcodes.co.uk/v1/keys/*', route => route.continue());
await page.goto('/index.php?route=checkout/checkout');
await page.waitForResponse('https://api.ideal-postcodes.co.uk/v1/keys/*');

The route.continue() handler is a no-op (you can just remove it). More importantly, waitForResponse is registered after goto() resolves — if the response already fired during navigation, the call will hang until the default timeout. The idiomatic Playwright pattern is:

const keysResponse = page.waitForResponse('https://api.ideal-postcodes.co.uk/v1/keys/*');
await page.goto('/index.php?route=checkout/checkout');
await keysResponse;

tests/admin.spec.ts:22 — silent fallback masks missing API_KEY.

const apiKey = process.env.API_KEY || '';

If the secret is missing in CI, the test will fill the API-key field with an empty string and fail later in a confusing way (likely on the verify-config step). Fail fast at module scope: if (!process.env.API_KEY) throw new Error('API_KEY env var is required');.

tests/admin.spec.ts:38-53afterAll does a wasteful second login.
Because the page fixture is not available in afterAll, you spin up a new context and re-login. The module-scoped token is still valid server-side, but you would still need a fresh context with the session cookie set. Worth a code comment explaining why the re-login is needed, otherwise it reads as accidental. Alternatively, move cleanup into a worker-scoped fixture teardown.

tests/admin.spec.ts:23-26 — module-scoped mutable let token is fragile.

let token: string;
...
test.beforeEach(async ({ page, baseURL }) => {
  if (token) return;
});

This works today only because workers: 1 and fullyParallel: false. The moment someone enables parallelism it will silently break (each worker re-imports the module and starts with token = undefined). Either accept the cost of logging in per test, or use a worker-scoped Playwright fixture.

Code quality / consistency

package.json:62dotenv version range is inconsistent and suspect.
Every other devDep uses ~ (patch only); dotenv uses ^17.4.2. Also worth double-checking that v17 is the intended major — recent stable was v16.x. Suggest \"dotenv\": \"~16.4.5\" (or whichever 16.x line you are targeting) to stay consistent.

CLAUDE.md Skills section is stale.
/test - Run Cypress tests should say Playwright.

tests/helpers/suite.ts and tests/admin.spec.ts — multiple hard-coded waitForTimeout calls.
These were direct ports of cy.wait(N) and are flaky by design. Replace with assertion-based waits (expect(locator).toBeVisible(), expect(locator).toHaveValue(...), etc.) where possible. Specific spots:

  • tests/helpers/suite.ts:51, 54, 63, 68 — 1000–2000ms sleeps around country select / autocomplete
  • tests/admin.spec.ts:58, 77 — 2000ms / 1000ms sleeps around marketplace navigation and save

The waitForTimeout(2000) after selectOption('type') in admin.spec.ts is the most likely flake source on slow CI.

tests/admin.spec.ts:31getByText('Login') may match more than the button.
Cypress .contains(\"Login\") returns the first match; Playwright's locator is strict and will throw if multiple elements match. Probably fine on this page, but .getByRole('button', { name: 'Login' }) is safer.

.github/workflows/ci.yml and release.yml — Node 18 → 24.x is a sharp jump.
Confirm Node 24 LTS is appropriate for production. Worth setting engines.node in package.json so contributors and CI agree.

.gitignore — file is missing a trailing newline. Minor, but visible in the diff.

Mixed concerns in one PR.
This bundles four independent changes: (a) mastermain migration, (b) Cypress→Playwright port, (c) Node 18→24 bump, (d) .claude/ AI tooling. Each is easy to review in isolation; together they are hard to bisect if something breaks. Future PRs would benefit from being split.

Security

No new concerns. API_KEY is correctly sourced from secrets.API_KEY in CI, .env is gitignored, .env.example is a placeholder. The empty-string fallback in admin.spec.ts:22 is a robustness issue, not a security issue.

Performance

  • workers: 1 and fullyParallel: false keep the suite serial. Reasonable for now with one OpenCart container, flagging for future tuning.
  • retries: 2 on CI will mask flakes from the waitForTimeout calls above and inflate CI time — fix the flakes rather than relying on retries.

Test coverage

Coverage is preserved at parity with the Cypress suite (admin config navigation + checkout autocomplete/postcode lookup). No regression, no expansion. Neither suite covers the uninstall path or the OpenCart 4 install/upgrade flow exercised in src/admin/controller/module/ukaddresssearch.php — orthogonal to this PR but a known gap.


Net: directionally good, but I would want the waitForResponse race, the empty API_KEY fallback, and the release.config.js path array addressed before merge. The rest are quality nits.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (11)
tests/admin.spec.ts (3)

58-58: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

Using waitForTimeout(2000) introduces flakiness. Prefer waiting for specific conditions:

// Wait for network idle after dropdown selection
await page.waitForLoadState('networkidle');
// or wait for specific element that appears after selection
await page.waitForSelector('expected-element-selector');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/admin.spec.ts` at line 58, Replace the non-deterministic sleep call
page.waitForTimeout(2000) with a deterministic wait: after the dropdown
selection or action that triggers the change, use
page.waitForLoadState('networkidle') or await
page.waitForSelector('expected-element-selector') (replace with the actual
selector that signals completion) so the test waits for the specific network or
DOM condition instead of a fixed timeout.

77-77: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

Using waitForTimeout(1000) introduces flakiness. Wait for the save operation to complete by checking for success indicators:

// Wait for success message or page reload
await page.waitForLoadState('networkidle');
// or wait for toast/alert
await expect(page.locator('.alert-success')).toBeVisible();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/admin.spec.ts` at line 77, Replace the flaky explicit sleep call using
page.waitForTimeout(1000) with deterministic waits that assert the actual save
completion: remove page.waitForTimeout and instead wait for network idle via
page.waitForLoadState('networkidle') or assert a visible success indicator using
expect(page.locator('.alert-success')).toBeVisible(), targeting the same test
step where the save action occurs (look for usages around page.waitForTimeout
and the save action).

26-26: ⚡ Quick win

Token reuse depends on single-worker configuration.

The token caching with if (token) return; relies on tests running sequentially (workers: 1 in config). If the worker count increases, each worker will need to authenticate separately, and this shared token approach will fail.

Consider documenting this dependency or using Playwright's project setup to authenticate once and reuse the state across tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/admin.spec.ts` at line 26, The guard "if (token) return;" in the tests
relies on a single shared in-memory token and will break with multiple
Playwright workers; replace this pattern by either documenting the single-worker
requirement or, preferably, remove the global token cache and authenticate using
Playwright's supported mechanisms — e.g., perform login in a globalSetup or use
Playwright projects/storageState or a worker-scoped fixture so each worker
obtains and reuses its own auth state (replace the shared "token" guard with
per-worker storageState or a worker-scoped login helper).
tests/helpers/suite.ts (6)

11-14: 💤 Low value

normalizeCity only capitalises the first character.

The function converts to lowercase then capitalises only the first character, which won't handle multi-word city names (e.g., "ST HELIER" → "St helier" instead of "St Helier").

If the test fixtures already use proper casing and OpenCart only modifies the first letter, this is fine. Otherwise, consider proper title-casing:

const normalizeCity = (city: string): string => {
  return city.toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/suite.ts` around lines 11 - 14, normalizeCity currently
lowercases the whole string then only capitalises the first character, which
fails for multi-word city names; update the normalizeCity implementation to
title-case each word (e.g., use a regex replacement like replace(/\b\w/g, c =>
c.toUpperCase()) or split/map/join) so "ST HELIER" becomes "St Helier" while
still normalising case consistently.

51-51: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

Instead of waiting 1 second after country selection, wait for the form to be ready:

await page.waitForLoadState('networkidle');
// or wait for specific country-dependent elements to update
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/suite.ts` at line 51, Replace the non-deterministic sleep at
page.waitForTimeout(1000) in tests/helpers/suite.ts with a deterministic wait:
remove the waitForTimeout call and instead wait for a reliable readiness signal
after the country selection (e.g., call page.waitForLoadState('networkidle') or
page.waitForSelector for the specific country-dependent element or form field
that updates). Ensure the replacement targets the same test flow where country
selection occurs so functions that rely on the updated form state proceed only
after the actual UI/network work completes.

68-68: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

Instead of waiting 1 second after clicking the lookup button, wait for the results to populate:

await page.waitForSelector('.idpc-select-container select option:not([value=""])', { state: 'attached' });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/suite.ts` at line 68, Replace the brittle sleep in
tests/helpers/suite.ts (the await page.waitForTimeout(1000) call) with a
deterministic wait that waits for the lookup results to render; specifically,
after triggering the lookup action use page.waitForSelector to wait for
'.idpc-select-container select option:not([value=""])' to be attached so the
test proceeds only when options are populated.

64-64: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

Instead of waiting 1 second after country selection, wait for the country-dependent form updates:

await page.waitForLoadState('networkidle');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/suite.ts` at line 64, The hard-coded sleep using
page.waitForTimeout(1000) is flaky; replace the call to page.waitForTimeout in
the test helper with a deterministic wait such as awaiting the page network idle
state: use await page.waitForLoadState('networkidle') (or alternatively
waitForResponse/waitForSelector for the specific country-dependent element)
instead of page.waitForTimeout to ensure the form updates are complete after
country selection.

42-42: 💤 Low value

Document the hard-coded country code.

The country code '257' is hard-coded but its meaning (presumably Great Britain in OpenCart's system) is not obvious. Consider adding a comment:

await expect(scope.locator(selectors.country)).toHaveValue('257'); // GBR in OpenCart
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/suite.ts` at line 42, The assertion uses a hard-coded country
code '257' which is unclear; update the test at the
expect(scope.locator(selectors.country)).toHaveValue('257') site to clarify the
value by either replacing '257' with a named constant (e.g.,
DEFAULT_COUNTRY_GBR) or adding an inline comment such as "// GBR in OpenCart" so
future readers understand that '257' corresponds to Great Britain; touch the
expect call, the selectors.country reference, and any nearby test setup to use
the constant if you choose that approach.

54-54: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

Instead of waiting 2 seconds for autocomplete suggestions, wait for the suggestions to appear:

await page.waitForSelector('.idpc_ul li', { state: 'visible' });

This is more reliable and often faster than arbitrary timeouts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/suite.ts` at line 54, Replace the arbitrary sleep call using
page.waitForTimeout(2000) with a deterministic wait for the autocomplete
suggestions to become visible: use Playwright's waitForSelector (or
locator.waitFor) to wait for the suggestion list selector ('.idpc_ul li') to be
visible before proceeding; update the code path that calls page.waitForTimeout
to instead await page.waitForSelector('.idpc_ul li', { state: 'visible' }) so
tests become deterministic and faster.
tests/checkout.spec.ts (1)

27-27: ⚡ Quick win

Replace waitForTimeout with deterministic waits.

After clicking "Guest Checkout" and verifying the shipping address is visible, waiting 1 second is arbitrary. Consider waiting for the form to be ready for interaction:

await expect(page.locator('`#shipping-address` input').first()).toBeEnabled();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/checkout.spec.ts` at line 27, The current arbitrary sleep using
page.waitForTimeout(1000) should be replaced with a deterministic wait so the
test proceeds only when the shipping form is ready; remove the waitForTimeout
call and instead assert the shipping form input is interactive by awaiting the
page locator for '`#shipping-address` input' to be enabled/visible (use the
Playwright expect matcher such as toBeEnabled/toBeVisible) after the "Guest
Checkout" click and shipping address visibility check, so subsequent
interactions are stable and deterministic.
playwright.config.ts (1)

16-16: ⚡ Quick win

Consider making baseURL configurable.

The baseURL is hardcoded to http://localhost:3000. Consider using an environment variable to allow different environments:

baseURL: process.env.BASE_URL || 'http://localhost:3000',

This would align with the existing pattern of using .env for configuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@playwright.config.ts` at line 16, Replace the hardcoded baseURL string in
playwright.config.ts with a value read from the environment (use environment
variable BASE_URL with a fallback to 'http://localhost:3000') so tests can run
against different environments; update the baseURL assignment in the export (the
baseURL property) and ensure the process.env is populated (import or initialize
dotenv or import 'dotenv/config' near the top of the config file if not
already).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/admin.spec.ts`:
- Line 3: The parameter type for the helper function activateExtension is too
permissive: replace the any type on the page parameter with the Playwright Page
type; import Page from '`@playwright/test`' at the top of the file and update the
function signature (activateExtension(page: Page, token: string) => ...) and any
other local uses that declared page as any to use Page for proper type safety
and autocompletion.

---

Nitpick comments:
In `@playwright.config.ts`:
- Line 16: Replace the hardcoded baseURL string in playwright.config.ts with a
value read from the environment (use environment variable BASE_URL with a
fallback to 'http://localhost:3000') so tests can run against different
environments; update the baseURL assignment in the export (the baseURL property)
and ensure the process.env is populated (import or initialize dotenv or import
'dotenv/config' near the top of the config file if not already).

In `@tests/admin.spec.ts`:
- Line 58: Replace the non-deterministic sleep call page.waitForTimeout(2000)
with a deterministic wait: after the dropdown selection or action that triggers
the change, use page.waitForLoadState('networkidle') or await
page.waitForSelector('expected-element-selector') (replace with the actual
selector that signals completion) so the test waits for the specific network or
DOM condition instead of a fixed timeout.
- Line 77: Replace the flaky explicit sleep call using page.waitForTimeout(1000)
with deterministic waits that assert the actual save completion: remove
page.waitForTimeout and instead wait for network idle via
page.waitForLoadState('networkidle') or assert a visible success indicator using
expect(page.locator('.alert-success')).toBeVisible(), targeting the same test
step where the save action occurs (look for usages around page.waitForTimeout
and the save action).
- Line 26: The guard "if (token) return;" in the tests relies on a single shared
in-memory token and will break with multiple Playwright workers; replace this
pattern by either documenting the single-worker requirement or, preferably,
remove the global token cache and authenticate using Playwright's supported
mechanisms — e.g., perform login in a globalSetup or use Playwright
projects/storageState or a worker-scoped fixture so each worker obtains and
reuses its own auth state (replace the shared "token" guard with per-worker
storageState or a worker-scoped login helper).

In `@tests/checkout.spec.ts`:
- Line 27: The current arbitrary sleep using page.waitForTimeout(1000) should be
replaced with a deterministic wait so the test proceeds only when the shipping
form is ready; remove the waitForTimeout call and instead assert the shipping
form input is interactive by awaiting the page locator for '`#shipping-address`
input' to be enabled/visible (use the Playwright expect matcher such as
toBeEnabled/toBeVisible) after the "Guest Checkout" click and shipping address
visibility check, so subsequent interactions are stable and deterministic.

In `@tests/helpers/suite.ts`:
- Around line 11-14: normalizeCity currently lowercases the whole string then
only capitalises the first character, which fails for multi-word city names;
update the normalizeCity implementation to title-case each word (e.g., use a
regex replacement like replace(/\b\w/g, c => c.toUpperCase()) or split/map/join)
so "ST HELIER" becomes "St Helier" while still normalising case consistently.
- Line 51: Replace the non-deterministic sleep at page.waitForTimeout(1000) in
tests/helpers/suite.ts with a deterministic wait: remove the waitForTimeout call
and instead wait for a reliable readiness signal after the country selection
(e.g., call page.waitForLoadState('networkidle') or page.waitForSelector for the
specific country-dependent element or form field that updates). Ensure the
replacement targets the same test flow where country selection occurs so
functions that rely on the updated form state proceed only after the actual
UI/network work completes.
- Line 68: Replace the brittle sleep in tests/helpers/suite.ts (the await
page.waitForTimeout(1000) call) with a deterministic wait that waits for the
lookup results to render; specifically, after triggering the lookup action use
page.waitForSelector to wait for '.idpc-select-container select
option:not([value=""])' to be attached so the test proceeds only when options
are populated.
- Line 64: The hard-coded sleep using page.waitForTimeout(1000) is flaky;
replace the call to page.waitForTimeout in the test helper with a deterministic
wait such as awaiting the page network idle state: use await
page.waitForLoadState('networkidle') (or alternatively
waitForResponse/waitForSelector for the specific country-dependent element)
instead of page.waitForTimeout to ensure the form updates are complete after
country selection.
- Line 42: The assertion uses a hard-coded country code '257' which is unclear;
update the test at the
expect(scope.locator(selectors.country)).toHaveValue('257') site to clarify the
value by either replacing '257' with a named constant (e.g.,
DEFAULT_COUNTRY_GBR) or adding an inline comment such as "// GBR in OpenCart" so
future readers understand that '257' corresponds to Great Britain; touch the
expect call, the selectors.country reference, and any nearby test setup to use
the constant if you choose that approach.
- Line 54: Replace the arbitrary sleep call using page.waitForTimeout(2000) with
a deterministic wait for the autocomplete suggestions to become visible: use
Playwright's waitForSelector (or locator.waitFor) to wait for the suggestion
list selector ('.idpc_ul li') to be visible before proceeding; update the code
path that calls page.waitForTimeout to instead await
page.waitForSelector('.idpc_ul li', { state: 'visible' }) so tests become
deterministic and faster.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88782c3c-442a-46d5-8a01-cff35b07cc77

📥 Commits

Reviewing files that changed from the base of the PR and between ffa056d and b4a4e10.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • .claude/skills/test/SKILL.md
  • .env.example
  • .github/workflows/ci.yml
  • .gitignore
  • CLAUDE.md
  • cypress.config.ts
  • cypress/e2e/00_admin.ts
  • cypress/e2e/01_checkout.ts
  • cypress/fixtures/example.json
  • cypress/integration/01_checkout.ts
  • cypress/plugins/index.ts
  • cypress/support/commands.ts
  • cypress/support/e2e.ts
  • cypress/support/index.d.ts
  • cypress/support/index.ts
  • cypress/support/suite.ts
  • cypress/tsconfig.json
  • package.json
  • playwright.config.ts
  • tests/admin.spec.ts
  • tests/checkout.spec.ts
  • tests/helpers/suite.ts
💤 Files with no reviewable changes (12)
  • cypress/tsconfig.json
  • cypress/integration/01_checkout.ts
  • cypress/support/e2e.ts
  • cypress/support/index.d.ts
  • cypress/plugins/index.ts
  • cypress/support/index.ts
  • cypress/fixtures/example.json
  • cypress/e2e/00_admin.ts
  • cypress/support/commands.ts
  • cypress.config.ts
  • cypress/e2e/01_checkout.ts
  • cypress/support/suite.ts
✅ Files skipped from review due to trivial changes (3)
  • .env.example
  • CLAUDE.md
  • .claude/skills/test/SKILL.md

Comment thread tests/admin.spec.ts Outdated
mfilip added 2 commits May 29, 2026 14:05
- Update CLAUDE.md to reference Playwright tests instead of Cypress
- Add API_KEY validation in admin.spec.ts to fail fast if missing
- Replace arbitrary timeouts with explicit visibility waits
- Improve test reliability with proper wait conditions
- Add event registration backup in docker setup script
- Clean up release.config.js comments and asset path
- Downgrade dotenv from ^17.4.2 to ~16.4.5
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review

Thanks for the migration work. This PR is doing substantially more than the description suggests — alongside the CLAUDE.md + main-branch updates, it migrates the E2E suite from Cypress to Playwright, bumps Node 18 → 24 in CI, and changes the docker setup script. Worth calling out in the description so reviewers know what they're looking at.

🐛 Bugs / correctness

1. package.json and package-lock.json disagree on dotenv (high)

  • package.json declares "dotenv": "~16.4.5" (line 62)
  • package-lock.json resolves the root dotenv devDep to "^17.4.2" (line 39)
  • npm ci enforces consistency between these two and will fail. Re-run npm install to regenerate the lockfile, or align the package.json range to ^17.4.2.

2. npm test leaves containers running on failure (medium)

\"test\": \"npm start && npm run playwright && make down\"

With &&, if Playwright fails, make down never runs and containers stay up — bad in CI and locally. Suggest:

\"test\": \"npm start && (npm run playwright; status=$?; make down; exit $status)\"

or move teardown to a Playwright globalTeardown so it runs unconditionally.

3. Redundant event registration in docker/run-setup.php (medium)
callModuleInstall() now calls registerEvent() unconditionally as "backup (in case install() event registration fails)". The module's install() (src/admin/controller/module/ukaddresssearch.php:140) already calls model_setting_event->addEvent(). The new check-then-insert avoids inserting twice, but:

  • If install() is working, this is dead code obscuring the real flow.
  • If install() is not working, we should fix install() instead of papering over it in the setup harness — production installs go through install(), not this script.
  • The duplicated 'trigger' / 'action' / 'sort_order' values risk drifting from the canonical definition in the controller.

If a workaround is genuinely needed, please add a comment explaining the failure mode (with a linked issue) and a TODO to remove it.

4. release.config.js comment is stale (low)

// Replaces version number in readme.txt and uk-address-postcode-validation

Only src/install.json is replaced now. Looks copy-pasted from another project — please update or remove.

⚙️ CI / build

5. Node 18 → 24 jump is unflagged (medium)
Going from Node 18 to Node 24 in one step is a significant change for the project (semantic-release runners, Rollup, docker-compose orchestration). Worth a one-line note in the PR description for the future maintainer reading git log. Node 24 is the current Active LTS so the choice itself is fine.

6. Playwright browser cache not cached in CI (low)

- run: npx playwright install --with-deps chromium

Every CI run re-downloads the Chromium build. Consider an actions/cache step keyed on ~/.cache/ms-playwright and package-lock.json to shave ~30s off CI.

🧪 Tests

7. Module-scoped login token in tests/admin.spec.ts (low)

let token: string;
test.beforeEach(async ({ page }) => {
  if (token) return;
  ...
});

The comment correctly acknowledges this only works because of workers: 1 + fullyParallel: false. A worker-scoped fixture would be safer if anyone later flips parallelism on. Acceptable as-is given the comment, but consider promoting it now while the suite is small.

8. fullyParallel: false + workers: 1 is redundant (nit)
workers: 1 already serializes — fullyParallel: false is no-op here. Either is fine, both adds noise.

9. normalizeCity in tests/helpers/suite.ts (nit, pre-existing)

return lowerCase.charAt(0).toUpperCase() + lowerCase.slice(1);

Breaks on multi-word cities ("New York" → "New york"). Carried over from the Cypress version, so out of scope, but worth a follow-up.

10. Top-level throw in tests/admin.spec.ts (nit)

if (!process.env.API_KEY) {
  throw new Error('API_KEY environment variable is required');
}

This fires during file collection, which means a single missing env var aborts the whole test run rather than failing one suite. Prefer test.beforeAll(() => { test.skip(!process.env.API_KEY, '...'); }) or a worker fixture so the failure is scoped to admin tests.

🔐 Security

11. CLAUDE.md note on Twig json_encode is slightly wrong (medium for docs accuracy)

Use Twig's json_encode() filter for JS data ... must be escape-safe to prevent XSS

Twig's json_encode filter does not by itself prevent XSS when interpolated inside <script> blocks — it doesn't escape </script> sequences, and Twig's HTML autoescape doesn't apply to script context. A user-controlled string containing </script><script>alert(1)</script> can break out. Recommend documenting the actual pattern used in the codebase (e.g. json_encode|raw plus output filtering, or e('js')) rather than implying json_encode is safe on its own.

📝 Process

12. PR description omits major scope (process)
Cypress → Playwright is the largest behavioral change in this PR, and Node 24 + docker/run-setup changes also aren't mentioned. Please update the description before merging — it makes future bisects and changelog generation harder.


Overall: the Playwright migration looks well-executed, the .claude/skills and CLAUDE.md scaffolding is reasonable, and the master→main updates are consistent. Blocking items in my view: #1 (lockfile drift), #2 (CI container leak), and clarifying #3 (the "backup" event registration). Everything else is non-blocking polish.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
docker/run-setup.php (1)

339-348: ⚡ Quick win

Consider adding verification after insertion.

The addModuleToList function (lines 267-283) follows a pattern of verifying successful insertion by querying the inserted row. Applying the same pattern here would improve consistency and provide better confidence that the event was registered successfully.

♻️ Proposed verification pattern
         ");
         $eventId = $db->getLastId();
-        echo "Event registered with ID: {$eventId}\n";
+        
+        if ($eventId) {
+            echo "Event registered with ID: {$eventId}\n";
+            
+            // Verify the entry was added
+            $verify_query = $db->query("SELECT * FROM `" . DB_PREFIX . "event` WHERE `event_id` = '" . (int)$eventId . "'");
+            if ($verify_query->num_rows) {
+                $row = $verify_query->row;
+                echo "Verified in database: ID={$row['event_id']}, code={$row['code']}\n";
+            } else {
+                echo "ERROR: Could not verify the inserted event despite having an ID!\n";
+            }
+        } else {
+            echo "ERROR: Failed to register event! No ID returned.\n";
+        }
     } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker/run-setup.php` around lines 339 - 348, After inserting the event and
getting $eventId, perform a verification query similar to addModuleToList:
select the row from the event table using the returned $eventId and confirm a
result exists; if no row is found, log an error and handle/fail appropriately.
Update the insertion block that creates the ukaddresssearch event (the INSERT
and $eventId retrieval) to include a follow-up SELECT on DB_PREFIX . "event" for
that $eventId, check the result, and echo or process a failure path when
verification fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/checkout.spec.ts`:
- Around line 24-25: Replace the brittle sleep call (page.waitForTimeout) with a
deterministic wait for the postcode widget initialization: remove the await
page.waitForTimeout(2000) and instead wait for the selector '`#shipping-address`
.idpc_lookup[idpc="true"]' to be visible (use Playwright's
expect(locator).toBeVisible with an increased timeout, e.g. 15000) so the test
reliably proceeds only after the extension widget has marked the context as
initialized.

---

Nitpick comments:
In `@docker/run-setup.php`:
- Around line 339-348: After inserting the event and getting $eventId, perform a
verification query similar to addModuleToList: select the row from the event
table using the returned $eventId and confirm a result exists; if no row is
found, log an error and handle/fail appropriately. Update the insertion block
that creates the ukaddresssearch event (the INSERT and $eventId retrieval) to
include a follow-up SELECT on DB_PREFIX . "event" for that $eventId, check the
result, and echo or process a failure path when verification fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93276171-fe9e-4961-b1ad-ce46ab37d60b

📥 Commits

Reviewing files that changed from the base of the PR and between b4a4e10 and 345062a.

📒 Files selected for processing (7)
  • CLAUDE.md
  • docker/run-setup.php
  • package.json
  • release.config.js
  • tests/admin.spec.ts
  • tests/checkout.spec.ts
  • tests/helpers/suite.ts
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/helpers/suite.ts
  • package.json

Comment thread tests/checkout.spec.ts Outdated
- Replace 2s timeout with explicit wait for .idpc_lookup element
- Increase timeout to 15s for widget injection
- Improve test reliability with proper wait condition
- Update comment to clarify widget is hidden until UK selected
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review: Cypress → Playwright migration + main-branch rename

Overall this is a clean, well-scoped infrastructure migration. The Playwright ports preserve test semantics, add reasonable timeouts in place of cy.wait(), and the master→main rename is consistent across CI, release, Makefile, and README. A few items worth a look before merging:

🐛 Likely bugs / behavior changes

  1. docker/run-setup.phpregisterEvent() masks a real bug. The comment says "Register the event directly here as backup (in case install() event registration fails)". If the production install() controller can fail to register the ukaddresssearch event, real installations are silently broken; the docker test harness now hides that. The right fix is to debug why install() doesn't always register the event and fix that in src/admin/controller/extension/idealpostcodes/module/, not patch the test setup. This also duplicates production install logic that can drift (e.g. if the trigger/action ever changes).

  2. tests/admin.spec.ts — silent empty-token fallback. token = url.searchParams.get('user_token') || ''; will hand the test an empty string if the URL pattern ever shifts (e.g. OpenCart upgrade), producing confusing 404s downstream instead of a clear failure. Prefer throwing:

    const t = url.searchParams.get('user_token');
    if (!t) throw new Error('user_token missing from admin URL');
    token = t;
  3. tests/admin.spec.ts — module-scoped let token. The inline comment correctly flags this only works with workers: 1 + fullyParallel: false. That's fine today, but a test.beforeAll producing a worker-scoped fixture would be safer and removes the time-bomb if anyone bumps parallelism later.

  4. tests/helpers/suite.tsline3Exists branch silently skips. If selectors.line_3 is set but selectors.line_2 isn't, both branches do nothing for line_2 — a future selector tweak could quietly drop an assertion. Worth at least an explicit else if or an invariant comment.

🔒 Security

  • .env correctly gitignored, .env.example added — 👍
  • docker/run-setup.php SQL uses interpolated DB_PREFIX only (no user input) — not exploitable, but if any future caller passes arguments to registerEvent(), this pattern will be copy-pasted unsafely. Consider parameterized helpers if OpenCart's DB layer supports them.
  • CI passes API_KEY via secret — good. Note the rename means anyone with a fork running CI must also rename CYPRESS_API_KEYAPI_KEY in their secrets.

⚙️ Quality / consistency

  1. Node version jump 18 → 24 in ci.yml (and 22 → 24 in release.yml) is big — consider adding an engines.node field to package.json so local devs and tooling pick the same line.
  2. playwright.config.tsdotenv.config({ path: path.resolve(__dirname, '.env') }) works because Playwright loads the config as CJS, but a relative path: '.env' is enough and avoids the __dirname foot-gun if the config ever gets compiled to ESM.
  3. release.config.js — changed path: ["idealpostcodes.ocmod.zip"]path: "idealpostcodes.ocmod.zip". Both are valid per @semantic-release/github, but worth a quick dry-run release (npx semantic-release --dry-run --no-ci) to confirm the asset still uploads.
  4. .gitignore still missing trailing newline (pre-existing, but easy to fix while you're in there).

⚡ Performance

  • npx playwright install --with-deps chromium (single browser) — good choice; keeps CI fast.
  • workers: 1 is required given the shared OpenCart container — explicitly setting both workers and fullyParallel: false is the right call.

🧪 Test coverage

  • New suite mirrors the previously-running Cypress e2e/ specs (admin config + shipping checkout autocomplete + postcode lookup). The deleted cypress/integration/ files weren't matched by specPattern: 'cypress/e2e/**/*', so they were dead code — no regression.
  • Pre-existing gap (not introduced here): lib/billing.ts, lib/account.ts, lib/custom.ts, lib/extension.ts have no e2e coverage. Worth a follow-up issue.
  • Consider one negative test (e.g. invalid API key → expected error UI) to catch silent-failure regressions.

✨ Nice touches

  • Clear Suite interface and destructured params in the helper.
  • Fail-fast API_KEY check at module load in admin.spec.ts.
  • Replacing arbitrary cy.wait(2000) with bounded expect(...).toBeVisible({ timeout }) is a real improvement.

Recommendation: approve once the registerEvent() workaround is either justified in a code comment (with a linked tracking issue for the real install bug) or replaced by a fix in the production install path. Other items are non-blocking.

— Reviewed by Claude (claude-opus-4-7)

@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review: feat: add AI support structure and migrate to main branch

Thanks for the thorough Cypress → Playwright migration and the mastermain housekeeping. Overall this is a solid PR; the test rewrite is more idiomatic Playwright than a line-by-line port, the release.config.js array-vs-string fix is a real bug correction, and CLAUDE.md / skill docs are clear. A few items worth a second look before merging.

Blockers / Likely Bugs

1. Test coverage regression: billing-address checkout was not migrated

The old cypress/integration/01_checkout.ts covered the billing flow (lib/billing, scope #address), and the new tests/checkout.spec.ts only covers the shipping flow (lib/shipping, #shipping-address). lib/billing.ts still exists, so the production surface is unchanged — only the test was dropped. Please add a tests/checkout-billing.spec.ts mirror, or note in the PR description why the billing flow no longer needs coverage.

2. docker/run-setup.phpregisterEvent() as a "backup" masks a real bug

// Register the event directly here as backup (in case install() event registration fails)
registerEvent();

This duplicates logic the OpenCart install() controller is supposed to own, and the comment admits it's papering over a failure mode in install(). Worth investigating the root cause rather than registering a fallback path — otherwise both code paths drift, and prod installs (which don't run run-setup.php) would hit the same install() bug undetected. Minimum: change the comment to record why install() fails so the workaround can be removed later.

3. Implicit cross-file test ordering

tests/checkout.spec.ts only works because tests/admin.spec.ts runs first and configures the API key. This holds today by alphabetical file order + workers: 1, but is fragile (renaming a file, adding .only, or future parallelism breaks it silently with confusing errors). Prefer one of:

  • A globalSetup that configures the extension via DB/CLI before any spec runs.
  • Playwright project dependencies to make the order explicit.

Code Quality

tests/admin.spec.ts

  • module-scoped token comment is slightly misleading — it's describe-scoped. The if (token) return in beforeEach only works because there's one test; add a second test and the second beforeEach silently no-ops and reuses a potentially-expired token. Worker-scoped fixture is the right shape here even today.
  • await page.locator(...).clear() followed by .fill(...)fill() already clears. The clear() calls (admin.spec.ts:38, :43; suite.ts:53, :67) are redundant.
  • The selector a[href="${baseURL}/admin/index.php?route=extension/idealpostcodes/module/ukaddresssearch&user_token=${token}"] is built three times — extract to a const.

tests/helpers/suite.ts

  • await expect(scope.locator(selectors.country)).toHaveValue('222'); immediately after selectOption('222') is mostly redundant (selectOption throws if the option is missing). Fine as a wait barrier, just worth a one-line comment so a future reader doesn't delete it.

playwright.config.ts

  • reporter: 'html' in CI generates a report that isn't surfaced in GitHub Actions logs. Consider reporter: process.env.CI ? [['github'], ['html']] : 'html' so failures show up inline in the Actions UI.
  • No webserver / readiness check — assumes make bootstrap left the OpenCart container healthy. A small globalSetup poll on baseURL would make CI flakes much easier to triage.

CI / Workflow Concerns

node-version: '24.x' (was 18.x in CI, 22.x in release)

Jumping straight to Node 24 is aggressive — Node 22 is the current LTS as of mid-2026 and matches what semantic-release/playwright are most exercised against. Unless there's a specific reason for 24, I'd standardize on 22.x for both workflows. At minimum, the CI and release workflows should match (currently they both moved to 24 — good — but the jump itself is the question).

runs-on: blacksmith-4vcpu-ubuntu-2204

This is a third-party (paid) runner label. Confirm the org has Blacksmith configured and that this is an intentional cost commitment; otherwise CI will fail to schedule. Consider documenting the rationale in CLAUDE.md or the workflow file.

Missing permissions: blocks

Neither ci.yml nor release.yml declares least-privilege permissions. The release job in particular pushes commits, tags, and a GitHub release — it would benefit from an explicit permissions: contents: write (and issues: write if comment-on-release is used) rather than relying on the default token scope. CI can drop to contents: read.

retries: 2 in CI

With workers: 1 and 2 retries, a single flaky test can triple the suite's runtime budget. Fine for now given small suite size, but worth revisiting if the suite grows.

Security

  • API_KEY handling looks correct: loaded via dotenv, .env gitignored, .env.example template provided. No leakage in workflow logs.
  • registerEvent() SQL uses hardcoded literals plus DB_PREFIX — no injection risk.
  • CLAUDE.md note about Twig json_encode() for XSS is a nice touch.
  • Recommend adding permissions: blocks (see above) as defense in depth.

Nits

  • .gitignore and .env.example are missing trailing newlines.
  • release.config.js: dropping the array-wrapped path: [...] to path: "..." is the right fix — worth calling out in the PR description since it changes release behavior.
  • The Claude skill docs reference make setup-extension; worth verifying that target exists in the current Makefile (didn't see it in the diff context).

Summary

Blockers I'd want addressed before merge: (1) billing checkout coverage, (2) the install() event-registration bug that registerEvent() is papering over, and (3) an explicit signal (globalSetup or project deps) that admin must run before checkout. Everything else is polish.

- Add Node.js version requirement (>=24.0.0) to package.json
- Simplify dotenv path configuration in Playwright config
- Add null check for user_token extraction in admin tests
- Refactor address line assertions with clearer control flow and comments
- Expand registerEvent() documentation explaining test bootstrap workaround
- Fix .gitignore trailing newline
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review: feat: add AI support structure and migrate to main branch

Thanks for this work — the Cypress → Playwright migration is well-scoped and the AI tooling additions look clean. A few things worth considering before merging.

Code quality & best practices

  • tests/admin.spec.ts:12 — module-scoped let token. The closure-based token cache works only because of workers: 1 + fullyParallel: false. The inline comment acknowledges this, but the safer fix is a worker-scoped fixture or a test.beforeAll that yields the token via fixture — that way enabling parallelism in the future won't silently break login state.
  • Hidden ordering dependency between specs. checkout.spec.ts depends on the API key configured by admin.spec.ts. This works today only because Playwright runs spec files alphabetically (admin < checkout) with workers: 1. If anyone renames the admin spec or re-orders, checkout silently breaks on a non-obvious failure. Either combine them into one ordered file, or do the API-key configuration once in a globalSetup script so each spec is independently runnable.
  • Hardcoded country IDs ('222', '257') in tests/helpers/suite.ts and tests/admin.spec.ts. These are tied to the OpenCart seed data — a different OC version or any reordering of the country table will fail with an opaque "option not found." A named constant with a comment explaining what 222/257 map to (UK / Jersey?) would help future maintainers.
  • tests/admin.spec.ts:32baseURL interpolation in selectors. a[href="${baseURL}/admin/..."] requires the rendered link to be absolute and match baseURL exactly (including trailing slash handling). If OpenCart renders relative hrefs, this silently fails. Consider [href$="route=extension/idealpostcodes/module/ukaddresssearch&user_token=${token}"] instead.

Bugs / risk areas

  • docker/run-setup.php SQL workaround is a footgun. The new registerEvent() duplicates the event definition that already lives in src/admin/controller/module/ukaddresssearch.php. The comment says "keep in sync" — that drift will happen, and tests will still pass with stale data, masking exactly the kind of install bug this code is supposed to validate. Two better options:
    1. Track down the silent addEvent() failure (likely missing model_setting_event context when invoked outside the admin request lifecycle) and fix the root cause.
    2. If that's out of scope, at least re-query and assert the inserted event row matches a single source of truth (e.g., a JSON manifest both install() and the workaround read from), so divergence fails loudly.
  • Lost layout-activation coverage. The old cypress/e2e/00_admin.ts after() hook ran activateExtension(token) to add the module to layouts 6 and 7. The new specs don't exercise this. Confirm intentional — the new event-hook approach (catalog/view/common/header/after) appears to inject globally and bypass layout placement, but if admins still do layout-based placement in prod, that flow is now untested.
  • @playwright/test ~1.52.0 pin — make sure the lockfile keeps both @playwright/test and the underlying playwright package on compatible majors so npx playwright install doesn't pull a mismatched binary.

Performance

  • workers: 1 is fine for now (shared OpenCart state, single API key). Once tests grow it'll bottleneck — worth a TODO. The Playwright migration unlocks parallelism that Cypress couldn't do cheaply, so capturing that intent for later is useful.
  • retries: process.env.CI ? 2 : 0 + trace: 'on-first-retry' is a good default; no concerns.

Security

  • .env.example is fine. Confirm API_KEY is a low-privilege Ideal Postcodes test key (not a billable production key) since CI uses it on every push to any branch (push: branches: ['**']).
  • CI runs on blacksmith-4vcpu-ubuntu-2204 — third-party runner. Verify the org has Blacksmith configured and that secrets are scoped appropriately. Worth a line in CONTRIBUTING/README so external contributors know forks won't have the runner.
  • engines.node >= 24.0.0 + Node 24 in CI: Node 24 isn't LTS yet (Node 22 is the current LTS). The engines floor will warn on npm install for any contributor on 22 LTS, and break under --engine-strict. Recommend >=22.0.0 as the floor and pinning 24 only in CI, unless there's a hard runtime requirement on 24.

Test coverage

  • Net behavioral coverage: roughly equivalent (admin config + checkout postcode lookup + checkout autocomplete). Good port.
  • Gaps introduced:
    • Layout activation flow (above).
    • No direct assertion that the injectConfig event actually fires — checkout indirectly verifies it via .idpc_lookup being attached, which is fine, but a smoke test against a simpler storefront page would catch event-registration regressions faster.
  • tests/helpers/suite.ts is well-structured and easier to read than the Cypress original — nice cleanup.

Minor

  • release.config.js: branches: ["main"] — good. The path: ["idealpostcodes.ocmod.zip"]path: "idealpostcodes.ocmod.zip" change matches @semantic-release/github's expected shape. Worth a dry-run to confirm the asset still resolves.
  • .gitignore cleanup is good. .claude/settings.local.json and .claude/plans/ being ignored is sensible.
  • CLAUDE.md references skills /build, /test, /release — confirm those slugs still match .claude/skills/*/SKILL.md.
  • The SKILL.md files duplicate info from CLAUDE.md (commands, project structure). Not a blocker, but be aware of drift — consider linking from one to the other.

Summary

The migration itself is sound. Main asks before merge:

  1. Address the duplicated event definition in run-setup.php (root cause or assert-on-divergence).
  2. Reconsider engines.node >= 24.0.0 — 22 LTS is likely the right floor.
  3. Confirm the layout activation flow is genuinely obsolete, or add coverage for it.

Nice work overall — the Playwright code is more readable than the Cypress equivalent.

- Add development section to README with CI and testing information
- Document Blacksmith runner usage and fallback behavior
- Replace absolute href selectors with suffix selectors for better reliability
- Extract OpenCart country IDs as named constants (COUNTRY_UK, COUNTRY_JERSEY)
- Add inline documentation for country ID values from oc_country table
- Reuse extensionLink locator to reduce duplication in admin tests
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review: feat: add AI support structure and migrate to main branch

Thanks for the substantial migration work — moving Cypress → Playwright cleanly plus standardising on main is a nice cleanup. Most of the change is well-structured, but I have a handful of issues worth addressing before merging. Highlights below, organised by category.


Bugs / correctness

  1. npm test leaves containers running on failurepackage.json:30

    "test": "npm start && npm run playwright && make down"

    If playwright exits non-zero, make down never runs and the Docker stack lingers. Same risk in CI on cancellation. Consider:

    "test": "npm start && (npm run playwright; status=$?; make down; exit $status)"

    or split into pre/post scripts so cleanup is unconditional.

  2. tests/admin.spec.ts — module-scoped token is a hidden trap. The comment honestly flags it (workers:1 / fullyParallel:false required), but if someone later flips fullyParallel: true in playwright.config.ts:9, tests will silently race rather than fail loudly. Two safer options:

    • Convert to a worker-scoped fixture (test.extend) so the contract is enforced by Playwright.
    • At minimum, assert in beforeAll that workers === 1, so misconfiguration trips an early error rather than corrupting state.
  3. tests/helpers/suite.ts:15-18normalizeCity only handles single words. It lowercases the whole string then uppercases char 0. A post_town like "ST HELIER" would become "St helier" and the assertion would fail. If the fixture's post_town is always single-word this is fine, but it's a sharp edge waiting for a different fixture. Use per-word capitalisation or just compare case-insensitively.

  4. tests/checkout.spec.ts:20 — hard-coded product_id=43 depends on the seed data shipped with the demo OpenCart install. If the seed changes (or the demo data is regenerated) every checkout test breaks with a confusing 404. Worth either: deriving the product ID at runtime (e.g., scrape it from the homepage) or asserting it exists in beforeAll with a clear error message.

  5. docker/run-setup.php:344-367registerEvent() workaround: the comment is excellent and acknowledges the "why," but the SQL has no error handling — if the INSERT fails (different schema across OC versions, FK conflict, etc.) the script reports success. At minimum, check the return of $db->query() and getLastId() and surface a failure. Also worth filing an issue against the root cause (addEvent() silently failing under the bootstrap script's incomplete registry); ad-hoc DB inserts will drift from the controller's install() over time.

  6. tests/checkout.spec.ts vs tests/helpers/suite.ts:46 — semantic mismatch. The test sets the country to COUNTRY_UK (222) but assertions always expects COUNTRY_JERSEY (257). This works because the Jersey postcode causes the widget to switch country, but it's non-obvious from reading the test. Add a one-line comment in checkout.spec.ts explaining "Jersey postcode triggers country auto-switch from UK→Jersey," or assert the transition explicitly so the intent is captured in code.


Code quality

  1. CLAUDE.md:34 — the security guidance is subtly misleading.

    Use Twig's json_encode() filter for JS data (it's interpolated as object literals inside <script>, so must be escape-safe to prevent XSS)

    Twig's json_encode calls PHP json_encode, which does not escape </script> substrings in encoded strings. A user-controlled value containing </script><script>alert(1)</script> will break out of the script tag. The correct guidance is to use |json_encode(constant('JSON_HEX_TAG') b-or constant('JSON_HEX_AMP') b-or constant('JSON_HEX_APOS') b-or constant('JSON_HEX_QUOT')) or to inject the JSON via a data-* attribute and parse client-side. Worth either fixing the doc or auditing existing <script> interpolations.

  2. playwright.config.ts:5dotenv.config({ path: '.env' }) silently no-ops if .env is missing. Combined with the fail-fast in tests/admin.spec.ts:4-6, that's fine — but consider passing { override: false } explicitly and at least logging when no .env is loaded, so a developer who forgot to copy .env.example gets a clue.

  3. playwright.config.ts:15 — hard-coded baseURL: 'http://localhost:3000'. A small thing, but making this process.env.BASE_URL ?? 'http://localhost:3000' lets CI override without code change (and matches what your Cypress setup likely had).

  4. docker/run-setup.php uses global $registry throughout the new code. This isn't worse than the existing pattern in the file, but if you ever extract these helpers for testing it'll bite. Not blocking for this PR.

  5. README.md:9 — still says Tested on OpenCart 3.0.3.x but the rest of the migration (and CLAUDE.md) target OpenCart 4.x. Update for consistency.

  6. Skills .claude/skills/*/SKILL.md look good and concise. One nit: test/SKILL.md:18 documents npm run playwright as "requires running container" — worth saying explicitly that API_KEY must also be set in .env, since admin.spec.ts throws otherwise.


CI / workflow

  1. .github/workflows/ci.yml has no timeout. make bootstrap boots Docker + initialises OpenCart; if anything hangs the job will run the full 6h GitHub default. Add timeout-minutes: 20 (or whatever your typical run + buffer is).

  2. CI never tears down the docker stack on failure. Add an if: always() step running make down so subsequent runs on self-hosted/Blacksmith runners start from a clean slate.

  3. secrets.API_KEY is unavailable for fork PRs, so external contributors' PRs will fail at the Playwright step. Either gate the playwright job on github.event.pull_request.head.repo.full_name == github.repository with a friendly message, or document the limitation in README.md's Development section.

  4. CI triggers push: branches: ['**'] AND pull_request: branches: [main] — branches with open PRs run CI twice. Either drop the push trigger for non-main branches or filter to main (and rely on PR runs).

  5. release.yml doesn't run tests before release. Given semantic-release on push to main, a broken main will ship. Either run tests as a job dependency, or rely on branch protection requiring CI to pass.


Test coverage

The migration is roughly 1:1 with the previous Cypress suite, but a few gaps are worth noting (not necessarily blocking):

  • No test for module uninstall (the uninstall() method has its own try/catch + event deletion).
  • No assertion that the registerEvent workaround actually produced a working event (e.g., that the catalog page receives the injected config). A negative test where the event is missing → checkout widget absent would catch regressions in the bootstrap script.
  • No coverage of the disabled path (idealpostcodes_enabled = 0) — easy to break with a refactor.

Security

  • docker/run-setup.php raw SQL uses DB_PREFIX + hard-coded values: not injectable.
  • .env correctly gitignored; .env.example carries no secrets.
  • Admin creds hardcoded in tests are appropriate for the throwaway demo install.

No security concerns blocking merge.


Performance

  • workers: 1, fullyParallel: false is conservative; once the shared-token issue (Bump @rollup/plugin-node-resolve from 8.4.0 to 9.0.0 #2 above) is fixed via a fixture, the two test files could run in parallel and roughly halve wall-clock.
  • retries: 2 on CI is reasonable for network-flaky widget interactions.

Summary

Solid migration overall. Before merging I'd prioritise: (a) the cleanup-on-failure issues in npm test and CI, (b) the misleading XSS guidance in CLAUDE.md, (c) error handling in the registerEvent workaround, and (d) making the shared admin-token contract enforced rather than commented. Everything else is polish.

🤖 Generated with Claude Code

- Add always() cleanup step to CI workflow to ensure Docker teardown
- Fix test script to preserve exit status during cleanup
- Add worker parallelism validation in Playwright config
- Improve security documentation for JSON data in templates
- Add error handling to event registration in Docker setup
- Update comments to clarify sequential test execution requirements
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

Code Review

Thanks for the migration — this is a thorough sweep across CI, branch naming, test framework, and tooling docs. Below are the issues I think are worth addressing before merge, ordered by impact.

High impact

1. Billing checkout test was dropped in the migration (test coverage regression).
The old Cypress suite had two checkout specs:

  • cypress/e2e/01_checkout.ts → tested lib/shipping
  • cypress/integration/01_checkout.ts → tested lib/billing

The new Playwright suite only ports the shipping version (tests/checkout.spec.ts). lib/billing.ts is now untested end-to-end. Suggest adding tests/billing.spec.ts mirroring the shipping spec with scope: '#address' and selectors from ../lib/billing to preserve coverage.

2. Implicit cross-spec ordering dependency.
tests/admin.spec.ts configures the API key into the extension; tests/checkout.spec.ts only works once that's done. The order relies on alphabetical file discovery — Playwright doesn't guarantee that, and any future sharding or grep filter (--grep, --project) would silently break the storefront tests with a confusing failure mode (no autocomplete results). Consider either:

  • Moving the admin-config step into a globalSetup so checkout tests can run in isolation, or
  • Wrapping both files in a test.describe.configure({ mode: 'serial' }) parent that documents the dependency.

3. PW_WORKERS guard in playwright.config.ts is ineffective.

if (process.env.PW_WORKERS && parseInt(process.env.PW_WORKERS) > 1) {
  throw new Error('Parallel workers not supported: ...');
}

Playwright doesn't read PW_WORKERS — it reads --workers on the CLI (or just the workers field in config). Setting PW_WORKERS=4 won't actually raise workers, so this guard never fires for the case it's meant to catch, and someone passing --workers=4 will bypass it. The hardcoded workers: 1 is what's actually load-bearing; suggest dropping the misleading guard, or reading PLAYWRIGHT_WORKERS if you want a real check.

Medium impact

4. Docs disagree on OpenCart version.
README.md still says "Tested on OpenCart 3.0.3.x" while CLAUDE.md says "extension for OpenCart 4.x". The PHP namespaces (Opencart\Admin\Controller\Extension\…) are OpenCart 4 style, so the README is the stale one and should be updated as part of this PR.

5. registerEvent() SQL fallback in docker/run-setup.php masks a real bug.
The comment is honest about it being a workaround, but inserting raw SQL diverges from the framework's model_setting_event->addEvent() contract — if the event table ever gains columns or triggers, the fallback drifts silently. Two improvements would help:

  • Log loudly (e.g., to STDERR) when the fallback path fires, so it's visible in CI logs whether install() actually worked or the SQL backup did the work.
  • Add a TODO/issue link pointing at the root cause (likely missing registry context when $loader->controller() runs outside a real request). Right now the workaround risks becoming permanent.

6. Race in admin save verification.

await page.locator('button.btn.btn-primary[title="Save"]').click();
await expect(extensionLink).toBeVisible({ timeout: 10000 });
await extensionLink.click();

If the save fails (validation error, server error), the form page often stays open — and the form page may also contain extensionLink in the sidebar/breadcrumbs, depending on the OpenCart theme. The visibility check could pass without a successful save, then re-click on a stale link, and only the final toHaveValue would catch the regression. Prefer asserting a success indicator (.alert-success) or page.waitForURL(/marketplace\/extension/) before re-clicking.

7. Case-sensitivity workaround in callModuleInstall().

$fullModulePath = str_replace('Idealpostcodes', 'idealpostcodes', $fullModulePath);

This patches the symptom. Worth tracking down where the capital-I form is being produced upstream — once two str_replace-style fixes land in the same script, they tend to multiply.

Low impact / nits

8. engines: { node: ">=24.0.0" } is steep.
Node 24 is fine for the CI workflow, but pinning engines blocks contributors on Node 22 LTS from npm install cleanly (warning, not error — but noisy). Unless something in the toolchain genuinely needs Node 24, ">=22" would be friendlier.

9. Token-sharing pattern in tests/admin.spec.ts.

let token: string;
test.beforeEach(async ({ page }) => {
  if (token) return;
  // login...
});

This works today because there's only one test, but the canonical Playwright equivalent is test.beforeAll — it makes the "login once, reuse" intent explicit and survives someone adding a second test that needs a fresh page context.

10. CI doesn't cache Playwright browsers.
npx playwright install --with-deps chromium re-downloads on every run. With Blacksmith runners and frequent PRs, caching ~/.cache/ms-playwright keyed by the @playwright/test lockfile version is a quick win.

11. release.config.jspath array → string change.

-            path: ["idealpostcodes.ocmod.zip"],
+            path: "idealpostcodes.ocmod.zip",

@semantic-release/github accepts both, but glob support is documented for the string form. Worth a sentence in the PR description on why this changed, or revert if unintentional.

12. Unscoped autocomplete dropdown selector (pre-existing, carried over from Cypress).
runAutocompleteSuite uses page.locator('.idpc_ul li') rather than scoping to scope.locator(...). If a future test renders both billing and shipping widgets on the same page, this will pick the wrong dropdown. Not introduced here, but easy to fix while you're in the file.

Positives worth calling out

  • The /build, /test, /release skills + CLAUDE.md index are a nice on-ramp; the XSS note about JSON.parse('{{ data }}') is exactly the kind of project-specific guidance that belongs in CLAUDE.md.
  • test script correctly preserves exit status through make down cleanup — a common bug in npm start && playwright && make down chains.
  • .env correctly added to .gitignore alongside .env.example.
  • The Blacksmith runner caveat in the README protects fork contributors from a confusing CI failure.

Happy to dig into any of these further if helpful.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker/run-setup.php`:
- Around line 352-367: The current check only looks up by `code =
'ukaddresssearch'` and skips rows that exist even if their `trigger`,`action` or
`status` are wrong; modify the logic around the `$query` result for the DB table
(`DB_PREFIX . "event"`) to be self-healing: if `$query->num_rows` is zero INSERT
the correct row (as you already do and capture `$eventId = $db->getLastId()`),
but if a row exists compare its `trigger`, `action`, `status` (and
`description`/`sort_order` if desired) and perform an UPDATE to set the correct
values when they differ; ensure you update the existing row via an UPDATE query
using the existing `$query->row['event_id']`, and echo whether you inserted,
updated, or left the event unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31ec595e-1d9b-4626-ba56-15a00b0b8fc5

📥 Commits

Reviewing files that changed from the base of the PR and between b6bed9a and 9256d0c.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • CLAUDE.md
  • docker/run-setup.php
  • package.json
  • playwright.config.ts
  • tests/admin.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/ci.yml
  • playwright.config.ts
  • package.json
  • tests/admin.spec.ts

Comment thread docker/run-setup.php
- Add browser caching to CI workflow with cache key based on package-lock.json
- Split browser installation into cached and dependency-only steps
- Move OpenCart bootstrap after browser installation for better workflow order
- Update README to reflect OpenCart 4.x testing
- Enhance event registration to update existing rows if values differ
- Add expected values documentation in registerEvent() function
- Simplify Playwright config by removing validation code
@claude

claude Bot commented May 29, 2026

Copy link
Copy Markdown

PR Review: feat: add AI support structure and migrate to main branch

Thanks for this work — the Cypress → Playwright migration is a solid modernization and the AI scaffolding/docs are useful. Below are concerns grouped by impact. Headline issues are (1) and (3).


1. docker/run-setup.php: install steps run twice (real bug)

installExtension() already calls addModuleToList, grantPermissions, callModuleInstall, enableModule, and forceClearCache on success (lines ~221-236). But the top-level script also calls all five of these again on lines 146-158. Net effect:

  • callModuleInstall() runs twice → $loader->controller(... '|install') is invoked twice → registerEvent() runs twice
  • Two "Ideal Postcodes extension setup completed successfully!" log lines (one at L238, one at L160)
  • Relying on every step being idempotent to hide it

Suggested fix: either remove the inner call cluster from installExtension() (keep it as a pure "register extension" function) or remove the outer calls in the main script. Pick one entry point.

2. installExtension() returns 0 on failure, caller ignores it

installExtension($extensionCode);            // can return 0 on failure
addModuleToList($extensionCode, ...);        // runs anyway
grantPermissions($fullModulePath);           // runs anyway

If extension registration fails (missing install.json, parse error, DB write failure), the script logs ERROR: but continues, producing a half-installed state and exiting 0. CI will report green. Recommend checking the return and exiting non-zero on failure (and possibly invoking exit(1) in the error branches inside installExtension).

3. registerEvent() workaround masks a real framework-bootstrap gap

The doc comment is honest about why the workaround exists (addEvent() silently fails in the bootstrap context), but two follow-ups:

  • It's called unconditionally after install(), not as a fallback. If install() ever starts working in this context, we'd be issuing a needless INSERT/UPDATE on every run. Idempotency check rescues us here, but the comment ("as backup") is misleading.
  • More importantly: "the event model's addEvent() silently fails - likely due to incomplete framework initialization" suggests other install steps may also be silently failing. If you can identify the missing registry component (probably session/user/url), fixing that would obviate this workaround and likely expose other latent failures. Worth a follow-up issue at minimum.
  • The hardcoded values (description, trigger, action) now live in two places — the controller's install() and this script. The TODO-style comment about keeping them in sync is fragile. Consider extracting to a constant or parsing install.json / the controller.

4. tests/admin.spec.ts: token caching breaks under retries

let token: string;
test.beforeEach(async ({ page }) => {
  if (token) return;            // short-circuits on retry
  ...
});

With retries: 2 in CI, when a test fails Playwright creates a fresh BrowserContext (new cookies/session). But the closure-scoped token persists. The retry skips login, then issues goto(...&user_token=<stale>) against a session-less browser — OpenCart will redirect to the login page and the test fails in a confusing way. Recommend either:

  • Re-logging in on every test (drop the cache), or
  • Persisting auth via storageState so the token and the cookie session both survive retries (globalSetup writing to a state file is the canonical pattern).

5. CI: blacksmith-4vcpu-ubuntu-2204 has no fallback

README claims: "CI will fall back to standard GitHub-hosted runners if unavailable." But the workflow has a hardcoded runs-on: blacksmith-4vcpu-ubuntu-2204 with no matrix/fallback. Fork PRs from external contributors will simply not run CI (the job will queue indefinitely or fail to start). Either:

  • Remove the misleading claim from the README, or
  • Wire up an actual fallback (matrix strategy with continue-on-error, or label-based selection).

6. Node 18 → 24 + engines.node: \">=24.0.0\"

Node 24 is the latest current — pinning >=24 rules out 22 LTS users entirely. If there's no specific Node-24 dependency, >=22.0.0 would broaden compatibility without losing anything material. Also worth noting Playwright 1.52 explicitly supports Node 18+, so this isn't required by the testing stack.

7. Playwright cache key is too coarse

key: playwright-${{ hashFiles('package-lock.json') }}

Any lockfile change invalidates the browser cache. Hashing only Playwright-related entries (e.g., a constant version file, or jq extracting @playwright/test from lockfile) would dramatically improve cache hit rate. Minor — current setup is correct, just not optimal.

8. playwright.config.ts: actionTimeout: 10000 may be tight for CI

OpenCart running in a docker container behind a fresh DB tends to be slow under CI load. 10s actionTimeout is fine locally but is probably why retries: 2 exists. Consider 20-30s for CI specifically — would reduce flaky-retry noise and the cost-of-retry problem flagged in (4).

9. PR scope

Six concerns bundled: framework migration (Cypress→Playwright), branch rename (master→main), Node version bump, install bug fix (registerEvent), CI runner switch (Blacksmith), AI documentation. Each is reviewable individually; together it's hard to bisect if something breaks. Suggesting separate PRs in the future — not blocking this one.


Smaller items

  • .gitignore: dropped the cypress.env.json and *.mp4 lines lost their trailing newline fix (now present). Good.
  • tests/helpers/suite.ts: nice cleanup — the typed Suite interface and explicit COUNTRY_* constants with comments are an improvement over the Cypress version.
  • tests/checkout.spec.ts:25: switching from cy.wait(1000) to expect(...).toBeAttached({ timeout: 15000 }) is exactly the right move.
  • CLAUDE.md security note about JSON.parse('{{ data }}') XSS is great — that kind of forward-looking guidance is the best use of CLAUDE.md.
  • docker/run-setup.php:319: str_replace('Idealpostcodes', 'idealpostcodes', $fullModulePath) is a code smell — if the input is ever fully uppercase or different-cased, this won't help. Better to lowercase the relevant segment at the source.
  • Trailing whitespace in ci.yml after cache-key lines — cosmetic but worth a pass with a linter.

Test coverage

  • No unit/integration coverage added for registerEvent()'s idempotency logic — the trickiest new code. A simple PHP-side test (or even just an integration assertion in admin.spec.ts that the event row exists with the right action) would catch regressions to the duplicate-install bug in (1).
  • Playwright suite still only exercises checkout shipping address. The billing-address path that the deleted cypress/integration/01_checkout.ts covered (#address scope) appears to be dropped. Intentional?

Summary

Approve-able with revisions. The duplicate install steps (1) and unchecked failure (2) are correctness bugs worth fixing before merge. Token-cache + retries (4) will likely surface as flaky CI later. Items (3) and (5-9) are valuable cleanup but could be follow-up PRs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 27: Replace the mutable tag "uses: actions/cache@v4" with a pinned commit
SHA for the actions/cache action; locate the uses: actions/cache@v4 entry in the
CI workflow and change the reference to the full commit SHA (e.g.,
actions/cache@<full-commit-sha>) so the workflow is immutable and meets stricter
supply-chain policies.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa2c1f88-6097-4e08-be06-2ad6418418b5

📥 Commits

Reviewing files that changed from the base of the PR and between 9256d0c and d1a1471.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • README.md
  • docker/run-setup.php
  • playwright.config.ts
  • tests/admin.spec.ts
  • tests/helpers/suite.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/helpers/suite.ts

Comment thread .github/workflows/ci.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant