test(e2e): migrate Cypress auth to @clerk/testing commands
This commit is contained in:
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -121,14 +121,24 @@ jobs:
|
||||
- name: Run Cypress E2E
|
||||
env:
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
# Vars for shared Clerk OTP helper (frontend/cypress/support/commands.ts)
|
||||
# Provide deterministic test creds directly (no secretless skipping).
|
||||
CYPRESS_CLERK_TEST_EMAIL: "jane+clerk_test@example.com"
|
||||
CYPRESS_CLERK_TEST_OTP: "424242"
|
||||
# Provide publishable key to Cypress so helper can derive CLERK_ORIGIN.
|
||||
CYPRESS_NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ vars.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
|
||||
# Clerk testing tokens (official @clerk/testing Cypress integration)
|
||||
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
|
||||
CLERK_PUBLISHABLE_KEY: ${{ vars.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
|
||||
# Also set for the app itself.
|
||||
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ vars.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
|
||||
CLERK_JWKS_URL: ${{ vars.CLERK_JWKS_URL }}
|
||||
# Test user identifier (used by cy.clerkSignIn)
|
||||
CYPRESS_CLERK_TEST_EMAIL: "jane+clerk_test@example.com"
|
||||
run: |
|
||||
cd frontend
|
||||
npm run e2e -- --browser chrome
|
||||
|
||||
- name: Upload Cypress artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cypress-artifacts
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
frontend/cypress/screenshots/**
|
||||
frontend/cypress/videos/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineConfig } from "cypress";
|
||||
import { clerkSetup } from "@clerk/testing/cypress";
|
||||
|
||||
export default defineConfig({
|
||||
env: {
|
||||
@@ -12,5 +13,8 @@ export default defineConfig({
|
||||
baseUrl: "http://localhost:3000",
|
||||
specPattern: "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}",
|
||||
supportFile: "cypress/support/e2e.ts",
|
||||
setupNodeEvents(on, config) {
|
||||
return clerkSetup({ config });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
describe("/activity feed", () => {
|
||||
const apiBase = "**/api/v1";
|
||||
const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com";
|
||||
|
||||
function stubStreamEmpty() {
|
||||
cy.intercept(
|
||||
@@ -21,18 +22,10 @@ describe("/activity feed", () => {
|
||||
cy.contains(/live feed/i, { timeout: 30_000 }).should("be.visible");
|
||||
}
|
||||
|
||||
it("auth negative: wrong OTP keeps us on sign-in", () => {
|
||||
// Start from app-origin sign-in to avoid cross-origin confusion.
|
||||
cy.visit("/sign-in");
|
||||
|
||||
// Override OTP just for this test.
|
||||
Cypress.env("CLERK_TEST_OTP", "000000");
|
||||
|
||||
// Expect login flow to fail; easiest assertion is that we remain on sign-in.
|
||||
// (The shared helper does not currently expose a typed hook to assert the error text.)
|
||||
cy.loginWithClerkOtp();
|
||||
|
||||
cy.location("pathname", { timeout: 30_000 }).should("match", /\/sign-in/);
|
||||
it("auth negative: signed-out user cannot access /activity", () => {
|
||||
// Story: signed-out user tries to visit /activity and is redirected to sign-in.
|
||||
cy.visit("/activity");
|
||||
cy.location("pathname", { timeout: 20_000 }).should("match", /\/sign-in/);
|
||||
});
|
||||
|
||||
it("happy path: renders task comment cards", () => {
|
||||
@@ -58,7 +51,9 @@ describe("/activity feed", () => {
|
||||
stubStreamEmpty();
|
||||
|
||||
// Story: user signs in, then visits /activity and sees the live feed.
|
||||
cy.loginWithClerkOtp();
|
||||
cy.visit("/sign-in");
|
||||
cy.clerkLoaded();
|
||||
cy.clerkSignIn({ strategy: "email_code", identifier: email });
|
||||
|
||||
cy.visit("/activity");
|
||||
assertSignedInAndLanded();
|
||||
@@ -77,7 +72,9 @@ describe("/activity feed", () => {
|
||||
stubStreamEmpty();
|
||||
|
||||
// Story: user signs in, then visits /activity and sees an empty-state message.
|
||||
cy.loginWithClerkOtp();
|
||||
cy.visit("/sign-in");
|
||||
cy.clerkLoaded();
|
||||
cy.clerkSignIn({ strategy: "email_code", identifier: email });
|
||||
|
||||
cy.visit("/activity");
|
||||
assertSignedInAndLanded();
|
||||
@@ -95,7 +92,9 @@ describe("/activity feed", () => {
|
||||
stubStreamEmpty();
|
||||
|
||||
// Story: user signs in, then visits /activity; API fails and user sees an error.
|
||||
cy.loginWithClerkOtp();
|
||||
cy.visit("/sign-in");
|
||||
cy.clerkLoaded();
|
||||
cy.clerkSignIn({ strategy: "email_code", identifier: email });
|
||||
|
||||
cy.visit("/activity");
|
||||
assertSignedInAndLanded();
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
describe("Clerk login (OTP)", () => {
|
||||
it("can sign in via Clerk modal", () => {
|
||||
// Skip unless explicitly configured.
|
||||
const clerkOrigin = Cypress.env("CLERK_ORIGIN");
|
||||
const email = Cypress.env("CLERK_TEST_EMAIL");
|
||||
const otp = Cypress.env("CLERK_TEST_OTP");
|
||||
describe("Clerk login", () => {
|
||||
it("user can sign in via Clerk testing commands", () => {
|
||||
const email = Cypress.env("CLERK_TEST_EMAIL") || "jane+clerk_test@example.com";
|
||||
|
||||
if (!clerkOrigin || !email || !otp) {
|
||||
cy.log("Skipping: missing CYPRESS_CLERK_ORIGIN / CYPRESS_CLERK_TEST_EMAIL / CYPRESS_CLERK_TEST_OTP");
|
||||
return;
|
||||
}
|
||||
// Prereq per Clerk docs: visit a non-protected page that loads Clerk.
|
||||
cy.visit("/sign-in");
|
||||
cy.clerkLoaded();
|
||||
|
||||
cy.clerkSignIn({ strategy: "email_code", identifier: email });
|
||||
|
||||
// After login, user should be able to access protected route.
|
||||
cy.visit("/activity");
|
||||
cy.loginWithClerkOtp();
|
||||
|
||||
// After login, the SignedIn UI should render.
|
||||
cy.contains(/live feed/i, { timeout: 20_000 }).should("be.visible");
|
||||
cy.contains(/live feed/i, { timeout: 30_000 }).should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Cypress support file.
|
||||
// Place global hooks/commands here.
|
||||
|
||||
import "@clerk/testing/cypress";
|
||||
import "./commands";
|
||||
|
||||
48
frontend/package-lock.json
generated
48
frontend/package-lock.json
generated
@@ -26,6 +26,7 @@
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@clerk/testing": "^1.13.35",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
@@ -364,9 +365,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@clerk/backend": {
|
||||
"version": "2.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-2.29.7.tgz",
|
||||
"integrity": "sha512-OSfFQ85L0FV2wSzqlr0hRvluIu3Z5ClgLiBE6Qx7XjSGyJoqEvP5OP4fl5Nt5icgGvH0EwA1dljPGyQpaqbQEw==",
|
||||
"version": "2.30.1",
|
||||
"resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-2.30.1.tgz",
|
||||
"integrity": "sha512-GoxnJzVH0ycNPAGCDMfo3lPBFbo5nehpLSVFjgGEnzIRGGahBtAB8PQT7KM2zo58pD8apjb/+suhcB/WCiEasQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@clerk/shared": "^3.44.0",
|
||||
@@ -453,6 +454,34 @@
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@clerk/testing": {
|
||||
"version": "1.13.35",
|
||||
"resolved": "https://registry.npmjs.org/@clerk/testing/-/testing-1.13.35.tgz",
|
||||
"integrity": "sha512-y95kJZrMt0tvbNek1AWhWrNrgnOy+a53PSzHTHPF9d0kkOgzzu9l/Wq+Y0kBk6p64wtupYomeb7oVCQD7yCc0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@clerk/backend": "^2.30.1",
|
||||
"@clerk/shared": "^3.44.0",
|
||||
"@clerk/types": "^4.101.14",
|
||||
"dotenv": "17.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.17.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@playwright/test": "^1",
|
||||
"cypress": "^13 || ^14"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@playwright/test": {
|
||||
"optional": true
|
||||
},
|
||||
"cypress": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@clerk/types": {
|
||||
"version": "4.101.14",
|
||||
"resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.101.14.tgz",
|
||||
@@ -6634,6 +6663,19 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz",
|
||||
"integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@clerk/testing": "^1.13.35",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.1.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
|
||||
Reference in New Issue
Block a user