Skip to content

feat(e2e): add Playwright end-to-end tests for candidate application and job creation flows#62

Merged
JoachimLK merged 7 commits into
mainfrom
feat/playwright-tests
Feb 28, 2026
Merged

feat(e2e): add Playwright end-to-end tests for candidate application and job creation flows#62
JoachimLK merged 7 commits into
mainfrom
feat/playwright-tests

Conversation

@JoachimLK

@JoachimLK JoachimLK commented Feb 28, 2026

Copy link
Copy Markdown
Contributor
  • Added Playwright as a dependency and created a test script in package.json.
  • Created a GitHub Actions workflow for running E2E tests on pull requests.
  • Implemented critical flow tests for candidate application and job creation.
  • Introduced shared fixtures for managing test accounts and authenticated pages.
  • Configured Playwright settings and web server for local development.

Summary

  • What does this PR change?
  • Why is this needed?

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Chore

Validation

  • I tested locally
  • I added/updated relevant documentation
  • I verified multi-tenant scoping and auth behavior for affected API paths

DCO

  • All commits in this PR are signed off (Signed-off-by) via git commit -s

Summary by CodeRabbit

  • Tests

    • Added end-to-end tests covering job creation, publishing, and candidate application flows.
    • Added test fixtures for per-worker accounts and authenticated sessions.
    • Added Playwright configuration with reporting, retries, and artifact capture.
  • Chores

    • Added CI workflow to run e2e tests on PRs and manually.
    • Added npm script and dev dependencies to support e2e testing.
    • Updated ignore rules to exclude Playwright test artifacts.
  • Bug Fixes

    • Disabled rate limiting in non-production environments to improve local developer/testing stability.

…and job creation flows

- Added Playwright as a dependency and created a test script in package.json.
- Created a GitHub Actions workflow for running E2E tests on pull requests.
- Implemented critical flow tests for candidate application and job creation.
- Introduced shared fixtures for managing test accounts and authenticated pages.
- Configured Playwright settings and web server for local development.
@railway-app

railway-app Bot commented Feb 28, 2026

Copy link
Copy Markdown

🚅 Deployed to the reqcore-pr-62 environment in applirank

Service Status Web Updated (UTC)
applirank ✅ Success (View Logs) Feb 28, 2026 at 5:10 pm

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 14:07 Destroyed
@coderabbitai

coderabbitai Bot commented Feb 28, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Playwright end-to-end testing: CI workflow, Playwright config and fixtures, two critical-flow test suites, npm script/devDeps, .gitignore entries for test artifacts, and a development bypass for API rate limiting.

Changes

Cohort / File(s) Summary
CI Workflow
.github/workflows/e2e-tests.yml
New GitHub Actions workflow to run Playwright E2E on PRs/manual dispatch: starts PostgreSQL service, sets env vars, installs Node 20, runs drizzle-kit migrations, builds and starts Nuxt in production mode, runs Playwright, and uploads artifacts.
Playwright Config & Scripts
playwright.config.ts, package.json
Adds Playwright configuration (sequential runs, reporters, CI adjustments, optional local webServer) and npm script test:e2e; adds @playwright/test and wait-on as devDependencies and an override.
E2E Fixtures
e2e/fixtures.ts
Introduces TestAccount interface, generateTestAccount, and Playwright fixtures testAccount and authenticatedPage; re-exports expect.
E2E Test Specs
e2e/critical-flows/job-creation.spec.ts, e2e/critical-flows/candidate-application.spec.ts
Adds two end-to-end test suites covering job creation/publish/verification and candidate application flow with API response checks and recruiter-side verification.
Ignore Playwright Artifacts
.gitignore
Appends Playwright artifact directories to ignore: test-results/, playwright-report/, blob-report/, playwright/.cache/.
Server Rate-limit Short-circuit
server/middleware/api-rate-limit.ts, server/api/public/jobs/[slug]/apply.post.ts
Skips rate limiting when NODE_ENV is not production (early return in middleware and conditional invocation in apply handler).

Sequence Diagram(s)

sequenceDiagram
  participant GH as "GitHub Actions (e2e-tests)"
  participant DB as "PostgreSQL service"
  participant S3 as "S3 emulation"
  participant Runner as "CI Runner (Node 20)"
  participant App as "Nuxt App (localhost:3000)"
  participant PW as "Playwright (Browser)"

  GH->>DB: start DB container & run healthchecks
  GH->>S3: configure S3 emulation env
  GH->>Runner: checkout + setup Node 20
  Runner->>DB: run drizzle-kit migrations
  Runner->>Runner: install deps & build app
  Runner->>App: start server (production mode)
  PW->>App: wait for http://localhost:3000
  PW->>App: run tests using PLAYWRIGHT_BASE_URL
  PW->>DB: read/write during tests
  PW->>S3: interact with S3 emulation
  GH->>GH: upload artifacts (playwright-report, test-results)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through CI with tiny paws,
Spun up browsers, ticked the laws,
Jobs went live and forms were sent,
Reports harvested, bytes well spent—
🥕 I nibbled logs and danced because.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding Playwright end-to-end tests for candidate application and job creation flows, which aligns with the primary purpose of the PR.
Description check ✅ Passed The description includes a bulleted summary of changes and retains the template structure with checkboxes, though the Summary section questions remain unfilled and validation items are unchecked.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/playwright-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 4

🧹 Nitpick comments (1)
e2e/fixtures.ts (1)

6-8: Align fixture scope with the documented behavior.

Line 6-8 says account generation is per worker, but Line 41 sets fixture scope to 'test'. Please either update the comment or switch to worker scope to avoid future confusion.

Also applies to: 41-41

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/fixtures.ts` around lines 6 - 8, The comment states account generation is
"per worker" but the fixture is defined with scope 'test'; update one to match
the other: either change the comment to say fixtures are per-test, or change the
fixture scope from 'test' to 'worker' so account generation is truly per-worker;
locate the fixture declaration that sets scope to 'test' (the fixture where
scope: 'test' is assigned) and modify the scope string to 'worker' if you choose
the latter, or edit the comment text at the top to reflect per-test behavior if
you choose to keep scope 'test'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/e2e-tests.yml:
- Around line 36-39: Replace the hardcoded BETTER_AUTH_SECRET env value with a
runtime-generated secret: remove the fixed string for BETTER_AUTH_SECRET and add
a preceding workflow step that generates a random 32+ char secret (e.g. via
openssl rand -base64 32 or head -c32 /dev/urandom | base64) and exports it to
GITHUB_ENV so the env key BETTER_AUTH_SECRET is injected at runtime; apply the
same change to other occurrences of the hardcoded secret (the other env block
using BETTER_AUTH_SECRET/BETTER_AUTH_URL) so no secret-like values remain in the
checked-in YAML.

In `@e2e/critical-flows/candidate-application.spec.ts`:
- Around line 63-67: The test currently calls
candidatePage.context().clearCookies(), which clears cookies for the shared
Playwright context and invalidates the recruiter's auth; instead create an
isolated context for the candidate using browser.newContext(), e.g. const
candidateContext = await browser.newContext(); const candidatePage = await
candidateContext.newPage(); perform any cookie clearing or session setup only on
candidateContext (or omit if new), and close candidateContext after the
candidate flows so the original shared context (and recruiterPage/auth used
elsewhere) remain intact.

In `@e2e/fixtures.ts`:
- Around line 36-37: Replace the empty destructuring pattern in the async
fixture signature "async ({}, use, workerInfo) =>" with a named parameter (or
remove it) to satisfy Biome's noEmptyPattern rule; update the parameter to
something like "async (_opts, use, workerInfo) =>" or drop the first parameter
if it's unused so that the fixture function (the async anonymous fixture
initializer) no longer uses an empty object pattern.

In `@playwright.config.ts`:
- Line 18: The reporter configuration currently sets reporter: process.env.CI ?
'github' : 'html', which in CI only emits GitHub annotations and doesn't create
the playwright-report/ artifact; update the reporter setting so that when
process.env.CI is true it includes both the 'github' reporter and the 'html'
reporter (ensuring html writes to the default output folder) — locate the
reporter property in playwright.config.ts and change it to return an array
containing the github reporter and the html reporter for CI builds.

---

Nitpick comments:
In `@e2e/fixtures.ts`:
- Around line 6-8: The comment states account generation is "per worker" but the
fixture is defined with scope 'test'; update one to match the other: either
change the comment to say fixtures are per-test, or change the fixture scope
from 'test' to 'worker' so account generation is truly per-worker; locate the
fixture declaration that sets scope to 'test' (the fixture where scope: 'test'
is assigned) and modify the scope string to 'worker' if you choose the latter,
or edit the comment text at the top to reflect per-test behavior if you choose
to keep scope 'test'.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4592981 and d47ad5a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • .github/workflows/e2e-tests.yml
  • .gitignore
  • e2e/critical-flows/candidate-application.spec.ts
  • e2e/critical-flows/job-creation.spec.ts
  • e2e/fixtures.ts
  • package.json
  • playwright.config.ts

Comment on lines +36 to +39
env:
DATABASE_URL: postgresql://reqcore:reqcore-ci@localhost:5432/reqcore
BETTER_AUTH_SECRET: ci-test-secret-that-is-at-least-32-chars-long
BETTER_AUTH_URL: http://localhost:3000

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid committing fixed auth secrets in workflow YAML.

Line 38 embeds a secret-like value in source control. Generate it at runtime instead to reduce leakage/compliance risk and secret-scanner noise.

Suggested fix
     env:
       DATABASE_URL: postgresql://reqcore:reqcore-ci@localhost:5432/reqcore
-      BETTER_AUTH_SECRET: ci-test-secret-that-is-at-least-32-chars-long
       BETTER_AUTH_URL: http://localhost:3000
       NUXT_PUBLIC_SITE_URL: http://localhost:3000
@@
     steps:
       - name: Checkout
         uses: actions/checkout@v4
+
+      - name: Generate ephemeral auth secret
+        run: echo "BETTER_AUTH_SECRET=$(openssl rand -hex 32)" >> "$GITHUB_ENV"

Also applies to: 49-58

🧰 Tools
🪛 Checkov (3.2.334)

[medium] 37-38: Basic Auth Credentials

(CKV_SECRET_4)

🪛 Gitleaks (8.30.0)

[high] 38-38: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e-tests.yml around lines 36 - 39, Replace the hardcoded
BETTER_AUTH_SECRET env value with a runtime-generated secret: remove the fixed
string for BETTER_AUTH_SECRET and add a preceding workflow step that generates a
random 32+ char secret (e.g. via openssl rand -base64 32 or head -c32
/dev/urandom | base64) and exports it to GITHUB_ENV so the env key
BETTER_AUTH_SECRET is injected at runtime; apply the same change to other
occurrences of the hardcoded secret (the other env block using
BETTER_AUTH_SECRET/BETTER_AUTH_URL) so no secret-like values remain in the
checked-in YAML.

Comment thread e2e/critical-flows/candidate-application.spec.ts Outdated
Comment thread e2e/fixtures.ts
Comment on lines +36 to +37
// eslint-disable-next-line no-empty-pattern
async ({}, use, workerInfo) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant section
if [ -f "e2e/fixtures.ts" ]; then
  echo "=== File exists, reading lines 30-45 ==="
  cat -n e2e/fixtures.ts | sed -n '30,45p'
else
  echo "File does not exist"
fi

Repository: reqcore-inc/reqcore

Length of output: 621


🏁 Script executed:

# Check for Biome configuration
echo "=== Checking for Biome/ESLint configs ==="
ls -la | grep -E "(biome|eslint|\.lint)" || echo "No obvious config files"
find . -maxdepth 2 -type f -name "biome.json" -o -name ".eslintrc*" -o -name "package.json" | head -20

Repository: reqcore-inc/reqcore

Length of output: 195


🏁 Script executed:

# Look for noEmptyPattern rule in Biome or ESLint configs
echo "=== Checking biome.json for rules ==="
if [ -f "biome.json" ]; then
  cat biome.json | head -100
fi

Repository: reqcore-inc/reqcore

Length of output: 1875


🏁 Script executed:

# Check package.json for linter configuration
if [ -f "package.json" ]; then
  echo "=== Checking package.json for linter deps ==="
  grep -E "(biome|eslint)" package.json || echo "No linter deps found"
fi

Repository: reqcore-inc/reqcore

Length of output: 129


🏁 Script executed:

# Verify if noEmptyPattern is part of Biome's recommended rules
# by checking the Biome documentation or looking at rule details
echo "=== Checking Biome version and checking noEmptyPattern ==="
cat biome.json | grep -A 2 '"recommended"'

Repository: reqcore-inc/reqcore

Length of output: 375


🏁 Script executed:

# Let's also check if there are any other fixture files to understand the pattern
echo "=== Checking other fixture-related files ==="
find e2e -name "*.ts" -type f | head -10

Repository: reqcore-inc/reqcore

Length of output: 212


🏁 Script executed:

# Look for similar patterns in the codebase to understand if this is a known issue
echo "=== Checking for empty pattern usage in TypeScript files ==="
rg '\{\}' e2e/ --type ts -B 2 -A 2 | head -50

Repository: reqcore-inc/reqcore

Length of output: 397


🏁 Script executed:

# Check if there's a way to suppress Biome errors (different syntax than ESLint)
echo "=== Checking Biome suppress syntax documentation ==="
grep -r "biome-ignore\|@biome" e2e/ --include="*.ts" || echo "No Biome suppression comments found"

Repository: reqcore-inc/reqcore

Length of output: 151


🏁 Script executed:

# Let's verify the actual function signature of Playwright test fixtures
# to understand if the first parameter is truly unused or if there's a semantic reason
echo "=== Checking how testAccount fixture is used ==="
rg 'testAccount' e2e/ --type ts -B 2 -A 2 | head -40

Repository: reqcore-inc/reqcore

Length of output: 2252


🏁 Script executed:

# Check if there are any lint configurations that might affect how this is treated
echo "=== Checking for any override rules in correctness category ==="
cat biome.json | grep -A 10 '"correctness"'

Repository: reqcore-inc/reqcore

Length of output: 478


🏁 Script executed:

# Check for CI configuration to confirm Biome is run
echo "=== Checking for CI/linting configuration ==="
find . -maxdepth 3 -type f \( -name "*.yml" -o -name "*.yaml" \) | grep -E "(github|gitlab|circleci|pipeline)" | head -10

Repository: reqcore-inc/reqcore

Length of output: 388


🏁 Script executed:

# Check if there's a lint script in package.json
if [ -f "package.json" ]; then
  echo "=== Checking for lint scripts ==="
  cat package.json | grep -A 5 '"scripts"' | head -20
fi

Repository: reqcore-inc/reqcore

Length of output: 332


🏁 Script executed:

# Verify the exact Biome version and schema to confirm noEmptyPattern is in recommended rules
echo "=== Biome schema version ==="
cat biome.json | grep schema

Repository: reqcore-inc/reqcore

Length of output: 153


🏁 Script executed:

# Check the PR validation workflow to confirm if Biome is run
echo "=== Checking PR validation workflow ==="
cat .github/workflows/pr-validation.yml

Repository: reqcore-inc/reqcore

Length of output: 1056


🏁 Script executed:

# Let's also check if there's a specific linting step
echo "=== Checking for any biome lint command in workflows ==="
grep -r "biome" .github/workflows/ || echo "No Biome commands found in workflows"

Repository: reqcore-inc/reqcore

Length of output: 156


🏁 Script executed:

# Let's check the full package.json scripts to be absolutely sure there's no lint command
echo "=== Full scripts section ==="
cat package.json | grep -A 50 '"scripts"' | grep -B 50 '}'

Repository: reqcore-inc/reqcore

Length of output: 1660


🏁 Script executed:

# Verify if there might be a separate lint configuration or if Biome is used through a different mechanism
echo "=== Checking if Biome might be invoked elsewhere ==="
grep -r "noEmptyPattern" . --include="*.json" --include="*.ts" --include="*.js" 2>/dev/null || echo "No references to noEmptyPattern found"

Repository: reqcore-inc/reqcore

Length of output: 153


Replace empty object pattern in fixture to match Biome rules.

Line 37 uses an empty destructuring pattern {}, which Biome's recommended rules flag as an error (noEmptyPattern in the correctness category). While Biome linting is not currently enforced in CI, the code violates the configured rules and should be fixed to follow best practices.

Suggested fix
-    // eslint-disable-next-line no-empty-pattern
-    async ({}, use, workerInfo) => {
+    async (_fixtures, use, workerInfo) => {
       const account = generateTestAccount(workerInfo.workerIndex)
       await use(account)
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// eslint-disable-next-line no-empty-pattern
async ({}, use, workerInfo) => {
async (_fixtures, use, workerInfo) => {
const account = generateTestAccount(workerInfo.workerIndex)
await use(account)
},
🧰 Tools
🪛 Biome (2.4.4)

[error] 37-37: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/fixtures.ts` around lines 36 - 37, Replace the empty destructuring
pattern in the async fixture signature "async ({}, use, workerInfo) =>" with a
named parameter (or remove it) to satisfy Biome's noEmptyPattern rule; update
the parameter to something like "async (_opts, use, workerInfo) =>" or drop the
first parameter if it's unused so that the fixture function (the async anonymous
fixture initializer) no longer uses an empty object pattern.

Comment thread playwright.config.ts Outdated
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 14:15 Destroyed
… creation flows with improved element targeting and timeout adjustments
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 14:23 Destroyed
…us visibility and onboarding page navigation
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 14:40 Destroyed

@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: 3

♻️ Duplicate comments (1)
e2e/fixtures.ts (1)

36-37: ⚠️ Potential issue | 🟡 Minor

Replace empty destructuring pattern in fixture args.

Line 37 uses {} in the fixture signature, which violates the configured lint rule (noEmptyPattern).

Suggested fix
-    // eslint-disable-next-line no-empty-pattern
-    async ({}, use, workerInfo) => {
+    async (_fixtures, use, workerInfo) => {
       const account = generateTestAccount(workerInfo.workerIndex)
       await use(account)
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/fixtures.ts` around lines 36 - 37, Replace the empty destructuring
pattern in the fixture arrow function signature (the async ({}, use, workerInfo)
=> { ... } anonymous fixture) with a named unused parameter like _args (e.g.
async (_args, use, workerInfo) => { ... }) or a meaningful name such as args;
update all references inside the function if any (there likely are none), which
removes the empty pattern and satisfies the noEmptyPattern lint rule.
🧹 Nitpick comments (1)
e2e/fixtures.ts (1)

6-8: Align fixture documentation with actual scope.

Lines 6-8 say accounts are unique “per worker”, but Line 41 sets scope: 'test' (per test). Please update either the comment or the scope so behavior and docs match.

Also applies to: 41-41

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/fixtures.ts` around lines 6 - 8, The comment for the fixture in
e2e/fixtures.ts says accounts are unique "per worker" but the fixture is defined
with scope: 'test' (see the fixture declaration setting scope). Fix the mismatch
by either changing the fixture's scope from 'test' to 'worker' on the fixture
configuration, or update the comment text to state "per test" to reflect the
current scope; ensure you edit the comment near the fixture declaration and the
fixture config (scope value) consistently so the docs and the behavior match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/critical-flows/job-creation.spec.ts`:
- Line 55: The assertion currently uses page.getByText('open').first() which can
match unrelated text; instead scope the check to the job's status element by
locating the specific job row (e.g., the job title or identifier element) and
then querying its status child (or use a dedicated test-id like job-status) to
assert it becomes "open" with a timeout; update the test to target that specific
locator rather than a global getByText('open').first() so the assertion
validates the publish-state for the intended job.
- Around line 64-67: The test's page.evaluate fetch call currently calls
res.json() unconditionally which masks non-2xx responses; inside the
page.evaluate callback (the async (id) => { ... } that runs with jobId), check
res.ok after the fetch and if false throw or return a clear error (include
res.status and res.statusText and the requested id or URL) before calling
res.json(), so failures surface with a descriptive message instead of a JSON
parse error; update the code that assigns jobData accordingly to rely on this
explicit check.
- Line 30: The test uses a brittle positional selector
page.locator('textarea').first().fill(JOB_DESCRIPTION); replace this with a
stable selector targeting the description field (e.g., a data-testid, name, id,
or accessible label) so the test always fills the job description; update the
locator used by page.locator/page.getByLabelText (the call that currently uses
'textarea' and .first()) to reference that stable attribute and keep the
.fill(JOB_DESCRIPTION) call.

---

Duplicate comments:
In `@e2e/fixtures.ts`:
- Around line 36-37: Replace the empty destructuring pattern in the fixture
arrow function signature (the async ({}, use, workerInfo) => { ... } anonymous
fixture) with a named unused parameter like _args (e.g. async (_args, use,
workerInfo) => { ... }) or a meaningful name such as args; update all references
inside the function if any (there likely are none), which removes the empty
pattern and satisfies the noEmptyPattern lint rule.

---

Nitpick comments:
In `@e2e/fixtures.ts`:
- Around line 6-8: The comment for the fixture in e2e/fixtures.ts says accounts
are unique "per worker" but the fixture is defined with scope: 'test' (see the
fixture declaration setting scope). Fix the mismatch by either changing the
fixture's scope from 'test' to 'worker' on the fixture configuration, or update
the comment text to state "per test" to reflect the current scope; ensure you
edit the comment near the fixture declaration and the fixture config (scope
value) consistently so the docs and the behavior match.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb94048 and e6e7c13.

📒 Files selected for processing (4)
  • e2e/critical-flows/candidate-application.spec.ts
  • e2e/critical-flows/job-creation.spec.ts
  • e2e/fixtures.ts
  • playwright.config.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • e2e/critical-flows/candidate-application.spec.ts
  • playwright.config.ts


// ── Step 1: Fill in job details ──────────────────────
await page.getByLabel('Job title').fill(JOB_TITLE)
await page.locator('textarea').first().fill(JOB_DESCRIPTION)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use a stable selector for the description field.

Line 30 relies on positional selection (textarea().first()), which is brittle if another textarea is added and can fill the wrong field.

Suggested fix
-    await page.locator('textarea').first().fill(JOB_DESCRIPTION)
+    await page.getByRole('textbox', { name: /description/i }).fill(JOB_DESCRIPTION)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await page.locator('textarea').first().fill(JOB_DESCRIPTION)
await page.getByRole('textbox', { name: /description/i }).fill(JOB_DESCRIPTION)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/job-creation.spec.ts` at line 30, The test uses a brittle
positional selector page.locator('textarea').first().fill(JOB_DESCRIPTION);
replace this with a stable selector targeting the description field (e.g., a
data-testid, name, id, or accessible label) so the test always fills the job
description; update the locator used by page.locator/page.getByLabelText (the
call that currently uses 'textarea' and .first()) to reference that stable
attribute and keep the .fill(JOB_DESCRIPTION) call.

await publishButton.click()

// Wait for status to update
await expect(page.getByText('open').first()).toBeVisible({ timeout: 10_000 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tighten publish-state assertion to avoid false positives.

Line 55 checks a generic open text node, which can pass from unrelated UI text. Scope this assertion to the job status element (or assert a publish-state transition signal tied to this job) so the test validates the actual state change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/job-creation.spec.ts` at line 55, The assertion currently
uses page.getByText('open').first() which can match unrelated text; instead
scope the check to the job's status element by locating the specific job row
(e.g., the job title or identifier element) and then querying its status child
(or use a dedicated test-id like job-status) to assert it becomes "open" with a
timeout; update the test to target that specific locator rather than a global
getByText('open').first() so the assertion validates the publish-state for the
intended job.

Comment on lines +64 to +67
const jobData = await page.evaluate(async (id) => {
const res = await fetch(`/api/jobs/${id}`)
return res.json()
}, jobId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard API fetch failures before parsing JSON.

If /api/jobs/${id} returns non-2xx, this currently fails later/less clearly. Add an explicit res.ok check for better failure diagnostics and stability.

Suggested fix
     const jobData = await page.evaluate(async (id) => {
       const res = await fetch(`/api/jobs/${id}`)
+      if (!res.ok) {
+        throw new Error(`Failed to load job ${id}: ${res.status}`)
+      }
       return res.json()
     }, jobId)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/job-creation.spec.ts` around lines 64 - 67, The test's
page.evaluate fetch call currently calls res.json() unconditionally which masks
non-2xx responses; inside the page.evaluate callback (the async (id) => { ... }
that runs with jobId), check res.ok after the fetch and if false throw or return
a clear error (include res.status and res.statusText and the requested id or
URL) before calling res.json(), so failures surface with a descriptive message
instead of a JSON parse error; update the code that assigns jobData accordingly
to rely on this explicit check.

…creation flows with improved element targeting and API response handling
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 15:03 Destroyed
… creation flows with improved wait states and context handling
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 16:51 Destroyed

@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.

🧹 Nitpick comments (3)
e2e/critical-flows/candidate-application.spec.ts (2)

61-64: Add error handling for the internal API fetch.

If the job API returns an error or the job doesn't exist, jobData.slug would be undefined, causing subsequent test steps to fail with a confusing error. Consider adding a basic assertion.

Suggested improvement
     const jobData = await page.evaluate(async (id) => {
       const res = await fetch(`/api/jobs/${id}`)
+      if (!res.ok) throw new Error(`Failed to fetch job: ${res.status}`)
       return res.json()
     }, jobId)
+
+    expect(jobData.slug, 'Job slug should be defined').toBeTruthy()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/candidate-application.spec.ts` around lines 61 - 64, When
fetching job data inside the page.evaluate call (the fetch to `/api/jobs/${id}`
that returns jobData for jobId), add error handling and an assertion that the
response is successful and that jobData.slug exists before using it; modify the
page.evaluate to check res.ok and the parsed JSON for a slug (or throw/return a
clear error), and in the test assert that jobData and jobData.slug are defined
so subsequent steps fail with a clear message rather than a runtime undefined
error.

100-103: Consider using Playwright's toBeOK() assertion.

The status range check works, but Playwright provides expect(applyResponse).toBeOK() for validating 2xx responses more idiomatically.

Suggested simplification
-    // Verify the API responded successfully (200 or 201)
-    const applyStatus = applyResponse.status()
-    expect(applyStatus, `Apply API returned ${applyStatus}`).toBeGreaterThanOrEqual(200)
-    expect(applyStatus, `Apply API returned ${applyStatus}`).toBeLessThan(300)
+    // Verify the API responded successfully
+    expect(applyResponse).toBeOK()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/candidate-application.spec.ts` around lines 100 - 103,
Replace the manual status-range assertions on applyResponse with Playwright's
idiomatic assertion: instead of checking applyResponse.status() with
expect(...).toBeGreaterThanOrEqual(200) and toBeLessThan(300), call
expect(applyResponse).toBeOK(); update the assertion around the applyResponse
variable (used after Apply API call) to use toBeOK() for clearer, standard 2xx
validation.
server/api/public/jobs/[slug]/apply.post.ts (1)

44-47: Rate limiting disabled for all non-production environments.

This bypasses rate limiting in any environment where NODE_ENV !== 'production', including staging or CI. While acceptable for E2E test stability, consider a more targeted approach using a dedicated test flag (e.g., E2E_TESTING=true) to avoid inadvertently disabling rate limiting in staging environments that should mimic production behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/api/public/jobs/`[slug]/apply.post.ts around lines 44 - 47, Change the
unconditional NODE_ENV !== 'production' bypass to a targeted E2E test flag so
rate limiting is only skipped for automated tests: replace the current check
around applyRateLimit(event) with a condition that calls applyRateLimit unless
process.env.E2E_TESTING === 'true' (e.g., if (process.env.E2E_TESTING !==
'true') await applyRateLimit(event)); keep the applyRateLimit(event) call and
event variable unchanged and ensure tests set E2E_TESTING=true when they need to
bypass rate limits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@e2e/critical-flows/candidate-application.spec.ts`:
- Around line 61-64: When fetching job data inside the page.evaluate call (the
fetch to `/api/jobs/${id}` that returns jobData for jobId), add error handling
and an assertion that the response is successful and that jobData.slug exists
before using it; modify the page.evaluate to check res.ok and the parsed JSON
for a slug (or throw/return a clear error), and in the test assert that jobData
and jobData.slug are defined so subsequent steps fail with a clear message
rather than a runtime undefined error.
- Around line 100-103: Replace the manual status-range assertions on
applyResponse with Playwright's idiomatic assertion: instead of checking
applyResponse.status() with expect(...).toBeGreaterThanOrEqual(200) and
toBeLessThan(300), call expect(applyResponse).toBeOK(); update the assertion
around the applyResponse variable (used after Apply API call) to use toBeOK()
for clearer, standard 2xx validation.

In `@server/api/public/jobs/`[slug]/apply.post.ts:
- Around line 44-47: Change the unconditional NODE_ENV !== 'production' bypass
to a targeted E2E test flag so rate limiting is only skipped for automated
tests: replace the current check around applyRateLimit(event) with a condition
that calls applyRateLimit unless process.env.E2E_TESTING === 'true' (e.g., if
(process.env.E2E_TESTING !== 'true') await applyRateLimit(event)); keep the
applyRateLimit(event) call and event variable unchanged and ensure tests set
E2E_TESTING=true when they need to bypass rate limits.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6e7c13 and 9dfb2bb.

📒 Files selected for processing (6)
  • e2e/critical-flows/candidate-application.spec.ts
  • e2e/critical-flows/job-creation.spec.ts
  • e2e/fixtures.ts
  • playwright.config.ts
  • server/api/public/jobs/[slug]/apply.post.ts
  • server/middleware/api-rate-limit.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • playwright.config.ts
  • e2e/critical-flows/job-creation.spec.ts

… of recruiter dashboard and candidate details
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-62 February 28, 2026 17:06 Destroyed

@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)
e2e/critical-flows/candidate-application.spec.ts (2)

28-29: Unused testAccount parameter.

The testAccount fixture is destructured but not used in this test. Consider removing it from the parameter list to clarify the test's dependencies.

♻️ Suggested fix
-  test('candidate can apply to a published job', async ({ authenticatedPage, testAccount, browser }) => {
+  test('candidate can apply to a published job', async ({ authenticatedPage, browser }) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/candidate-application.spec.ts` around lines 28 - 29, The
test function candidate can apply to a published job declares an unused fixture
parameter testAccount; remove testAccount from the parameter list of the test
callback (the test declared with test('candidate can apply to a published job',
async ({ authenticatedPage, testAccount, browser }) => ...)) so the async
parameter destructuring becomes async ({ authenticatedPage, browser }) => ...,
and update any references if you later need that fixture elsewhere.

20-25: Consider generating unique applicant data per test run.

The hardcoded applicant email could cause conflicts if the application system enforces uniqueness constraints and the same test environment is reused across runs. While each test creates a fresh job via the unique testAccount fixture, generating a unique email (e.g., using a timestamp or worker index) would make the test more robust for repeated local runs or shared test environments.

♻️ Suggested approach
+import { test, expect } from '../fixtures'
+
+// Helper to generate unique applicant data
+const generateApplicant = (suffix: string) => ({
+  firstName: 'Jane',
+  lastName: 'Doe',
+  email: `jane.doe+${suffix}@example.com`,
+  phone: '+49 170 1234567',
+})
+
 const APPLICANT = {
   firstName: 'Jane',
   lastName: 'Doe',
   email: 'jane.doe@example.com',
   phone: '+49 170 1234567',
 }

Then inside the test, generate unique data:

const APPLICANT = generateApplicant(Date.now().toString())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/candidate-application.spec.ts` around lines 20 - 25,
APPLICANT currently uses a hardcoded email which can conflict across test runs;
change the test to generate a unique applicant object at runtime (e.g., create a
helper like generateApplicant or build the email using Date.now() or
Cypress.env('CI_NODE_INDEX')) and replace usages of the static APPLICANT
constant with the generated object; update references in the spec so functions
that consume APPLICANT (e.g., the application submission steps) use the new
generateApplicant(...) output to ensure a unique email per run.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/critical-flows/candidate-application.spec.ts`:
- Around line 58-66: Validate the extracted URL and API response before using
jobSlug: check page.url() and ensure jobId (from
jobDetailUrl.split('/dashboard/jobs/')[1]?.split('/')[0]) is defined and
non-empty, then call fetch(`/api/jobs/${id}`) and verify the response.ok and
that jobData.slug exists; if any check fails, throw or fail the test with a
clear message referencing jobDetailUrl, jobId, jobData and jobSlug so failures
surface immediately instead of producing undefined when navigating to
`/jobs/${jobSlug}`.
- Around line 133-136: The sidebar selector is fragile because the test uses
'[class*="fixed"]' in candidate-application.spec.ts; add a stable identifier
(e.g., data-testid="candidate-sidebar" or an ARIA role/label) to the sidebar
root in the component CandidateDetailSidebar.vue (the element rendering the
fixed sidebar around line ~305), then update the test's locator (the sidebar
constant) to use that attribute (e.g.,
locator('[data-testid="candidate-sidebar"]')) and keep the existing
getByRole(...).filter({ hasText: APPLICANT.firstName }) and subsequent
expectations unchanged.

---

Nitpick comments:
In `@e2e/critical-flows/candidate-application.spec.ts`:
- Around line 28-29: The test function candidate can apply to a published job
declares an unused fixture parameter testAccount; remove testAccount from the
parameter list of the test callback (the test declared with test('candidate can
apply to a published job', async ({ authenticatedPage, testAccount, browser })
=> ...)) so the async parameter destructuring becomes async ({
authenticatedPage, browser }) => ..., and update any references if you later
need that fixture elsewhere.
- Around line 20-25: APPLICANT currently uses a hardcoded email which can
conflict across test runs; change the test to generate a unique applicant object
at runtime (e.g., create a helper like generateApplicant or build the email
using Date.now() or Cypress.env('CI_NODE_INDEX')) and replace usages of the
static APPLICANT constant with the generated object; update references in the
spec so functions that consume APPLICANT (e.g., the application submission
steps) use the new generateApplicant(...) output to ensure a unique email per
run.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9dfb2bb and 7769ec0.

📒 Files selected for processing (1)
  • e2e/critical-flows/candidate-application.spec.ts

Comment on lines +58 to +66
// Get job slug via API
const jobDetailUrl = page.url()
const jobId = jobDetailUrl.split('/dashboard/jobs/')[1]?.split('/')[0]
const jobData = await page.evaluate(async (id) => {
const res = await fetch(`/api/jobs/${id}`)
return res.json()
}, jobId)

const jobSlug = jobData.slug

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for API response and URL parsing.

The job ID extraction and API call lack validation. If the URL format changes or the API returns an error, jobSlug will be undefined, causing confusing failures later when navigating to /jobs/${jobSlug}.

🛡️ Suggested improvement
     // Get job slug via API
     const jobDetailUrl = page.url()
     const jobId = jobDetailUrl.split('/dashboard/jobs/')[1]?.split('/')[0]
+    expect(jobId, 'Failed to extract job ID from URL').toBeTruthy()
+
     const jobData = await page.evaluate(async (id) => {
       const res = await fetch(`/api/jobs/${id}`)
+      if (!res.ok) {
+        throw new Error(`API returned ${res.status}`)
+      }
       return res.json()
     }, jobId)
 
     const jobSlug = jobData.slug
+    expect(jobSlug, 'Job data missing slug').toBeTruthy()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Get job slug via API
const jobDetailUrl = page.url()
const jobId = jobDetailUrl.split('/dashboard/jobs/')[1]?.split('/')[0]
const jobData = await page.evaluate(async (id) => {
const res = await fetch(`/api/jobs/${id}`)
return res.json()
}, jobId)
const jobSlug = jobData.slug
// Get job slug via API
const jobDetailUrl = page.url()
const jobId = jobDetailUrl.split('/dashboard/jobs/')[1]?.split('/')[0]
expect(jobId, 'Failed to extract job ID from URL').toBeTruthy()
const jobData = await page.evaluate(async (id) => {
const res = await fetch(`/api/jobs/${id}`)
if (!res.ok) {
throw new Error(`API returned ${res.status}`)
}
return res.json()
}, jobId)
const jobSlug = jobData.slug
expect(jobSlug, 'Job data missing slug').toBeTruthy()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/candidate-application.spec.ts` around lines 58 - 66,
Validate the extracted URL and API response before using jobSlug: check
page.url() and ensure jobId (from
jobDetailUrl.split('/dashboard/jobs/')[1]?.split('/')[0]) is defined and
non-empty, then call fetch(`/api/jobs/${id}`) and verify the response.ok and
that jobData.slug exists; if any check fails, throw or fail the test with a
clear message referencing jobDetailUrl, jobId, jobData and jobSlug so failures
surface immediately instead of producing undefined when navigating to
`/jobs/${jobSlug}`.

Comment on lines +133 to +136
const sidebar = page.locator('[class*="fixed"]').filter({ hasText: APPLICANT.firstName })
await expect(sidebar.getByRole('heading', { name: `${APPLICANT.firstName} ${APPLICANT.lastName}` })).toBeVisible({ timeout: 10_000 })
await expect(sidebar.getByRole('definition').filter({ hasText: APPLICANT.email })).toBeVisible()
await expect(sidebar.getByRole('definition').filter({ hasText: APPLICANT.phone })).toBeVisible()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's find the sidebar component in the source code
# Look for components that might render the candidate detail sidebar
fd -e vue -e tsx -e ts "sidebar" | grep -v e2e | grep -v node_modules | head -20

Repository: reqcore-inc/reqcore

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Search for "fixed" class usage and sidebar-related patterns
rg -n "fixed" --type ts --type tsx --type vue -g '!e2e/**' -B 2 -A 2 | grep -i sidebar | head -50

Repository: reqcore-inc/reqcore

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Search for data-testid attributes with sidebar or candidate
rg 'data-testid.*["\'].*sidebar|data-testid.*["\'].*candidate.*detail' --type ts --type tsx --type vue -g '!e2e/**' -B 1 -A 1

Repository: reqcore-inc/reqcore

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Look for ARIA roles like complementary, dialog, aside in components
rg 'role=["\'](?:complementary|dialog|aside|region)["\']' --type ts --type tsx --type vue -g '!e2e/**' -B 2 -A 2 | head -80

Repository: reqcore-inc/reqcore

Length of output: 273


🏁 Script executed:

#!/bin/bash
# Check the actual test file to understand the context better
cat -n e2e/critical-flows/candidate-application.spec.ts | sed -n '110,140p'

Repository: reqcore-inc/reqcore

Length of output: 1839


🏁 Script executed:

#!/bin/bash
# Read the CandidateDetailSidebar component
cat -n app/components/CandidateDetailSidebar.vue

Repository: reqcore-inc/reqcore

Length of output: 38951


🏁 Script executed:

#!/bin/bash
# Search for data-testid in Vue files
rg 'data-testid' app/components/ -B 1 -A 1 | head -50

Repository: reqcore-inc/reqcore

Length of output: 301


🏁 Script executed:

#!/bin/bash
# Search for "fixed" class in components
rg '\bfixed\b' app/components/CandidateDetailSidebar.vue -B 2 -A 2

Repository: reqcore-inc/reqcore

Length of output: 668


🏁 Script executed:

#!/bin/bash
# Look for role attributes in the sidebar component
rg 'role=' app/components/CandidateDetailSidebar.vue -B 1 -A 1

Repository: reqcore-inc/reqcore

Length of output: 45


Add test identifier to sidebar component for stable selector.

The selector [class*="fixed"] is fragile because it matches any element with "fixed" in its class name (both the sidebar and delete dialog use this class), relying on text filtering for specificity. This breaks with CSS refactoring.

The sidebar should have a data-testid attribute or ARIA role added to ensure the selector is stable and decoupled from styling:

♻️ Recommended changes

Update app/components/CandidateDetailSidebar.vue line 305:

-    <aside
+    <aside
       v-if="open"
+      data-testid="candidate-detail-sidebar"
       class="fixed top-0 right-0 z-40 h-full w-[640px] max-w-[calc(100vw-4rem)] border-l border-surface-200 dark:border-surface-800 bg-white dark:bg-surface-900 shadow-xl flex flex-col"
     >

Then update the test selector in e2e/critical-flows/candidate-application.spec.ts line 133:

-    const sidebar = page.locator('[class*="fixed"]').filter({ hasText: APPLICANT.firstName })
+    const sidebar = page.getByTestId('candidate-detail-sidebar')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/critical-flows/candidate-application.spec.ts` around lines 133 - 136, The
sidebar selector is fragile because the test uses '[class*="fixed"]' in
candidate-application.spec.ts; add a stable identifier (e.g.,
data-testid="candidate-sidebar" or an ARIA role/label) to the sidebar root in
the component CandidateDetailSidebar.vue (the element rendering the fixed
sidebar around line ~305), then update the test's locator (the sidebar constant)
to use that attribute (e.g., locator('[data-testid="candidate-sidebar"]')) and
keep the existing getByRole(...).filter({ hasText: APPLICANT.firstName }) and
subsequent expectations unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant