playwright.config.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { defineConfig, devices } from '@playwright/test'
  2. /**
  3. * End-to-end tests: the app in a real browser, against a real Postgres.
  4. *
  5. * The 190-odd vitest files mock Prisma, so they prove the actions think
  6. * correctly and nothing else. These prove the parts that only break when the
  7. * pieces are assembled: migrations, the session cookie, server actions wired to
  8. * forms, and the invoice numbering that customers actually see.
  9. *
  10. * Deliberately serial against one seeded database. Parallel workers sharing a
  11. * quote counter would fail on each other's numbers rather than on real bugs.
  12. */
  13. const baseURL = process.env.E2E_BASE_URL ?? 'http://127.0.0.1:3100'
  14. const port = new URL(baseURL).port || '3100'
  15. /** The database the harness is allowed to destroy. Never the dev one. */
  16. const databaseUrl = process.env.E2E_DATABASE_URL ?? ''
  17. /** Where the mail sink listens: SMTP for the app, HTTP for the specs. */
  18. const smtpPort = process.env.E2E_SMTP_PORT ?? '1025'
  19. const mailApiPort = process.env.E2E_MAIL_API_PORT ?? '8025'
  20. /**
  21. * A mail server that delivers nothing, so the specs can read what the app
  22. * posted. Started whether or not the suite starts the app: pointed at a
  23. * server somebody else launched, that server is told to send here too.
  24. */
  25. /** Where the stand-in payment vendor listens, for the app and for the specs. */
  26. const paymentPort = process.env.E2E_PAYMENT_PORT ?? '8026'
  27. const paymentSinkUrl = `http://127.0.0.1:${paymentPort}`
  28. /**
  29. * Stripe and PayPal as far as the app can tell, with a checkout page a spec
  30. * can pay on. Started in every mode, like the mail sink: a server somebody
  31. * else launched is told to use it through the two base URLs below.
  32. */
  33. const paymentSink = {
  34. command: 'npx tsx e2e/payment-sink.ts',
  35. url: `${paymentSinkUrl}/health`,
  36. reuseExistingServer: !process.env.CI,
  37. timeout: 60_000,
  38. stdout: 'pipe' as const,
  39. stderr: 'pipe' as const,
  40. env: { E2E_PAYMENT_PORT: paymentPort },
  41. }
  42. const mailSink = {
  43. command: 'npx tsx e2e/mail-sink.ts',
  44. url: `http://127.0.0.1:${mailApiPort}/health`,
  45. reuseExistingServer: !process.env.CI,
  46. timeout: 60_000,
  47. stdout: 'pipe' as const,
  48. stderr: 'pipe' as const,
  49. env: { E2E_SMTP_PORT: smtpPort, E2E_MAIL_API_PORT: mailApiPort },
  50. }
  51. export default defineConfig({
  52. testDir: './e2e',
  53. globalSetup: './e2e/global-setup.ts',
  54. timeout: 60_000,
  55. expect: { timeout: 10_000 },
  56. fullyParallel: false,
  57. workers: 1,
  58. forbidOnly: !!process.env.CI,
  59. retries: process.env.CI ? 1 : 0,
  60. reporter: process.env.CI
  61. ? [['github'], ['html', { open: 'never' }]]
  62. : [['list'], ['html', { open: 'never' }]],
  63. use: {
  64. baseURL,
  65. // Pinned, because selectors read visible text and the app speaks twelve
  66. // languages. The locale cookie is set alongside this in auth.setup.ts.
  67. locale: 'en-US',
  68. extraHTTPHeaders: { 'accept-language': 'en' },
  69. // Pinned for the same reason invoice dates are: a floating timezone turns
  70. // a date assertion into a coin toss either side of midnight.
  71. timezoneId: process.env.E2E_TZ ?? 'Europe/Oslo',
  72. trace: 'retain-on-failure',
  73. screenshot: 'only-on-failure',
  74. video: 'retain-on-failure',
  75. },
  76. projects: [
  77. { name: 'setup', testMatch: /auth\.setup\.ts/ },
  78. {
  79. name: 'chromium',
  80. use: { ...devices['Desktop Chrome'], storageState: 'e2e/.auth/owner.json' },
  81. dependencies: ['setup'],
  82. },
  83. ],
  84. /**
  85. * The mail sink always; the app only when E2E_BASE_URL is unset, so pointing
  86. * the suite at a running container (or a staging host) is a matter of
  87. * setting one variable.
  88. *
  89. * `next start` and not `next dev`: the dev server compiles routes on first
  90. * visit, which turns the first assertion in every spec into a timeout race,
  91. * and it is not the artifact that ships anyway.
  92. */
  93. webServer: process.env.E2E_BASE_URL
  94. ? [mailSink, paymentSink]
  95. : [
  96. mailSink,
  97. paymentSink,
  98. {
  99. // The database first, then the server, in one command: Playwright
  100. // starts this before global setup, and a server on an empty schema
  101. // fails the readiness check on every page.
  102. command: `npx tsx e2e/prepare-db.ts && npm run start -- --port ${port}`,
  103. url: baseURL,
  104. reuseExistingServer: !process.env.CI,
  105. // Sixty-odd migrations, the seed and its vehicle photos, then the
  106. // server: a cold CI runner needs longer than a warm laptop.
  107. timeout: process.env.CI ? 420_000 : 180_000,
  108. stdout: 'pipe',
  109. stderr: 'pipe',
  110. env: {
  111. E2E_DATABASE_URL: databaseUrl,
  112. DATABASE_URL: databaseUrl,
  113. NEXT_PUBLIC_APP_URL: baseURL,
  114. // Long and random enough that better-auth does not spend the run
  115. // warning about it. Throwaway: it signs sessions for a database
  116. // the harness resets, and CI generates its own per run.
  117. BETTER_AUTH_SECRET:
  118. process.env.BETTER_AUTH_SECRET ?? 'k3Qb8vZ1hN7pXtR2yJm5Ls9CwD4gFa6UeH0iOoT+PbY=',
  119. // The schedulers would otherwise tick through the run, writing to
  120. // the rows the specs are asserting on.
  121. DISABLE_BACKGROUND_JOBS: '1',
  122. // Demo mode blocks invites, billing and outbound messages. Tests want
  123. // the real behaviour, so it stays off.
  124. DEMO_MODE: 'false',
  125. // Three sign-ins per ten seconds is right for a workshop and wrong
  126. // for a suite that signs in on every test.
  127. AUTH_RATE_LIMIT: 'off',
  128. // `next start` also reads the developer's .env, which may say cloud.
  129. // Self-hosted unlocks every feature, which is what a suite that
  130. // exercises them needs; plan gates are a subject of their own.
  131. TORQVOICE_MODE: 'self-hosted',
  132. TZ: process.env.E2E_TZ ?? 'Europe/Oslo',
  133. // Mail goes to the sink instead of a provider. The app's SMTP
  134. // settings fall back to these when nothing is configured in the
  135. // database, which is how the seeded workshop is left.
  136. SMTP_HOST: '127.0.0.1',
  137. SMTP_PORT: smtpPort,
  138. SMTP_FROM_EMAIL: 'workshop@e2e.test',
  139. SMTP_SECURE: 'false',
  140. // Payments go to the stand-in vendor. Read only from the
  141. // environment, so nothing a workshop stores can redirect a key.
  142. STRIPE_API_BASE_URL: paymentSinkUrl,
  143. PAYPAL_API_BASE_URL: paymentSinkUrl,
  144. },
  145. },
  146. ],
  147. })