google-password.spec.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { expect, test } from '@playwright/test'
  2. import {
  3. chooseGoogleAccount,
  4. completeOnboarding,
  5. continueWithGoogle,
  6. registerGoogleAccount,
  7. routeGoogleToStandin,
  8. signInWithPassword,
  9. } from '../../support/cloud'
  10. import { ageSessionsOf, personWithEmail } from '../../support/db'
  11. import { fillSettled, settle } from '../../support/hydration'
  12. import { waitForMail } from '../../support/mail'
  13. /**
  14. * A password for someone who signed up with Google.
  15. *
  16. * Their account has none, so the Change password card had no current
  17. * password to check and answered "Credential account not found", and the
  18. * two-factor card walked them as far as a password field they could not fill
  19. * in. The account page now offers to set one instead.
  20. *
  21. * A password is a way into the account that outlives the session, so the
  22. * server only sets one straight after a sign-in. That is the path worth a
  23. * browser: the person is told to sign in again, is taken there, comes back to
  24. * the same page, and finishes. Then the password has to be one the sign-in
  25. * page accepts, which only shows with the auth pieces assembled.
  26. */
  27. // Signing up and onboarding a workshop takes longer than an ordinary test.
  28. test.describe.configure({ mode: 'serial', timeout: 180_000 })
  29. // A stranger's browser, every time.
  30. test.use({ storageState: { cookies: [], origins: [] } })
  31. const stamp = Date.now()
  32. const PERSON = `e2e-google-set-password-${stamp}@example.com`
  33. const FIRST = `E2e-first-${stamp}`
  34. const SECOND = `E2e-second-${stamp}`
  35. test.beforeEach(async ({ context }) => {
  36. await routeGoogleToStandin(context)
  37. })
  38. test.describe('someone who signed up with Google', () => {
  39. test('is offered to set a password, and is not asked for a current one', async ({ page }) => {
  40. await registerGoogleAccount({ email: PERSON, name: 'E2E Google Password' })
  41. await continueWithGoogle(page, '/auth/sign-up', PERSON)
  42. await page.waitForURL(/\/onboarding/, { timeout: 30_000 })
  43. await completeOnboarding(page, `E2E Google Password Garage ${stamp}`, { sampleData: false })
  44. await page.goto('/settings/account')
  45. await settle(page)
  46. await expect(page.getByText('Set a Password', { exact: true })).toBeVisible()
  47. await expect(page.getByRole('button', { name: 'Set Password', exact: true })).toBeVisible()
  48. await expect(page.locator('#currentPassword'), 'no current password to ask for').toHaveCount(0)
  49. // Two-factor confirms with the account's password, so it waits for one.
  50. await expect(page.getByRole('button', { name: 'Enable 2FA', exact: true })).toBeDisabled()
  51. await expect(page.getByText(/Set a password first/)).toBeVisible()
  52. })
  53. test('is sent to sign in again when the sign-in is not recent, and comes back to finish', async ({
  54. page,
  55. }) => {
  56. await continueWithGoogle(page, '/auth/sign-in', PERSON)
  57. await page.waitForURL((url) => !/^\/(auth|onboarding)/.test(url.pathname), { timeout: 30_000 })
  58. // The tab has been open since this morning.
  59. await ageSessionsOf(PERSON, 30)
  60. await page.goto('/settings/account')
  61. await settle(page)
  62. await fillSettled(page.locator('#newPassword'), FIRST)
  63. await fillSettled(page.locator('#confirmPassword'), FIRST)
  64. await page.getByRole('button', { name: 'Set Password', exact: true }).click()
  65. await expect(page.getByText('Sign in again before setting a password.')).toBeVisible()
  66. expect((await personWithEmail(PERSON)).providers, 'nothing was set').toEqual(['google'])
  67. // Out, through Google, and back to the page they were on.
  68. await page.getByRole('button', { name: 'Sign in again', exact: true }).click()
  69. await page.waitForURL(/\/auth\/sign-in\?redirect=%2Fsettings%2Faccount/, { timeout: 30_000 })
  70. await chooseGoogleAccount(page, PERSON)
  71. await page.waitForURL(/\/settings\/account/, { timeout: 30_000 })
  72. await settle(page)
  73. await fillSettled(page.locator('#newPassword'), FIRST)
  74. await fillSettled(page.locator('#confirmPassword'), FIRST)
  75. await page.getByRole('button', { name: 'Set Password', exact: true }).click()
  76. await expect(page.getByText(/Password set\./)).toBeVisible()
  77. // The card is the ordinary one from here on, and two-factor is open.
  78. await expect(page.locator('#currentPassword')).toBeVisible()
  79. await expect(page.getByRole('button', { name: 'Change Password', exact: true })).toBeVisible()
  80. await expect(page.getByRole('button', { name: 'Enable 2FA', exact: true })).toBeEnabled()
  81. expect((await personWithEmail(PERSON)).providers).toEqual(['credential', 'google'])
  82. // And the address is told, in case it was not them.
  83. const notice = await waitForMail(PERSON, { subject: /password was added/i })
  84. expect(notice.text).toContain('A password was just set on your Torqvoice account')
  85. })
  86. test('signs in with the email address and that password, and can change it', async ({ page }) => {
  87. await signInWithPassword(page, { email: PERSON, password: FIRST })
  88. await expect(page.getByText(`E2E Google Password Garage ${stamp}`).first()).toBeVisible()
  89. await page.goto('/settings/account')
  90. await settle(page)
  91. await fillSettled(page.locator('#currentPassword'), FIRST)
  92. await fillSettled(page.locator('#newPassword'), SECOND)
  93. await fillSettled(page.locator('#confirmPassword'), SECOND)
  94. await page.getByRole('button', { name: 'Change Password', exact: true }).click()
  95. await expect(page.getByText('Password changed', { exact: true })).toBeVisible()
  96. })
  97. test('signs in with the changed password, and Google still works too', async ({
  98. page,
  99. browser,
  100. }) => {
  101. await signInWithPassword(page, { email: PERSON, password: SECOND })
  102. await expect(page.getByText(`E2E Google Password Garage ${stamp}`).first()).toBeVisible()
  103. const other = await browser.newContext({ storageState: { cookies: [], origins: [] } })
  104. await routeGoogleToStandin(other)
  105. const viaGoogle = await other.newPage()
  106. await continueWithGoogle(viaGoogle, '/auth/sign-in', PERSON)
  107. await viaGoogle.waitForURL((url) => !/^\/(auth|onboarding)/.test(url.pathname), {
  108. timeout: 30_000,
  109. })
  110. await expect(viaGoogle.getByText(`E2E Google Password Garage ${stamp}`).first()).toBeVisible()
  111. await other.close()
  112. expect((await personWithEmail(PERSON)).users, 'still one person').toBe(1)
  113. })
  114. })