Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import '@testing-library/jest-dom'

import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { Provider } from 'kea'
import { router } from 'kea-router'
import { expectLogic } from 'kea-test-utils'

import { useMocks } from '~/mocks/jest'
import { initKeaTests } from '~/test/init'

import { PasswordResetComplete } from './PasswordResetComplete'
import { passwordResetLogic } from './passwordResetLogic'

describe('PasswordResetComplete', () => {
const resetRequest = jest.fn(() => [200, { success: true, email: 'user@example.com' }])

beforeEach(() => {
resetRequest.mockClear()
useMocks({
get: { '/api/reset/:uuid/': () => [200, {}] },
post: { '/api/reset/:uuid/': resetRequest },
})
initKeaTests()
router.actions.push('/reset/valid_user/valid_token')
})

afterEach(() => {
cleanup()
})

it('shows the mismatch error and does not send the reset when the passwords differ', async () => {
const { container } = render(
<Provider>
<PasswordResetComplete />
</Provider>
)
await expectLogic(passwordResetLogic).toDispatchActions(['validateResetTokenSuccess'])

fireEvent.change(container.querySelector('input[data-attr="password"]')!, {
target: { value: 'hedgE-hog-123%' },
})
fireEvent.change(container.querySelector('input[data-attr="password-confirm"]')!, {
target: { value: '1234567A' },
})
fireEvent.click(screen.getByText('Change my password'))

await expectLogic(passwordResetLogic).toDispatchActions(['submitPasswordResetFailure'])
expect(await screen.findByText('Passwords do not match')).toBeInTheDocument()
expect(resetRequest).not.toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { router } from 'kea-router'
import { expectLogic } from 'kea-test-utils'

import { useMocks } from '~/mocks/jest'
import { initKeaTests } from '~/test/init'

import { passwordResetLogic } from './passwordResetLogic'

describe('passwordResetLogic', () => {
let logic: ReturnType<typeof passwordResetLogic.build>

beforeEach(() => {
useMocks({
get: {
'/api/reset/:uuid/': () => [
400,
{ code: 'invalid_token', detail: 'This reset token is invalid or has expired.' },
],
},
})
initKeaTests()
logic = passwordResetLogic()
logic.mount()
})

afterEach(() => {
logic.unmount()
})

it.each([
Comment thread
gantoine marked this conversation as resolved.
Resolved
['passwords that do not match', 'hedgE-hog-123%', '1234567A', 'passwordConfirm', 'Passwords do not match'],
['a password under 8 characters', '123', '123', 'password', 'Must be at least 8 characters long'],
])('rejects %s', (_name, password, passwordConfirm, field, error) => {
logic.actions.setPasswordResetValues({ password, passwordConfirm })

expect(logic.values.passwordResetValidationErrors).toMatchObject({ [field]: error })
})

it('marks the reset link invalid when the API rejects its token', async () => {
router.actions.push('/reset/invalid_user/token')

await expectLogic(logic).toDispatchActions(['validateResetTokenSuccess'])
expect(logic.values.validatedResetToken).toMatchObject({ success: false })
expect(logic.values.validatedResetToken?.token).toBeUndefined()
})
})
11 changes: 0 additions & 11 deletions playwright/e2e/annotations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,6 @@ test.describe('Annotations', () => {
await page.goToMenuItem('annotations')
})

test('Annotations loaded', async ({ page }) => {
// Check that the annotations page loaded with key elements visible.
// Scope to the scene title, a bare 'Annotations' heading match also hits the
// empty-state "Welcome to Annotations!" h2 (substring match) and trips strict mode.
await expect(page.getByTestId('scene-name').getByRole('heading', { name: 'Annotations' })).toBeVisible()
// The setup empty state replaces the table until the first annotation exists, and
// both render a create button under this data-attr. Assert the attr rather than the
// label so the check holds whichever of the two the project is currently showing.
await expect(page.getByTestId('create-annotation')).toBeVisible()
})

test('Create annotation', async ({ page }) => {
// Wait for the create button to be visible before clicking
const createButton = page.getByTestId('create-annotation')
Expand Down
26 changes: 0 additions & 26 deletions playwright/e2e/auth-password-reset.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,6 @@ test.describe('Password Reset', () => {
await expect(page.getByText(resetEmail)).toBeVisible()
})

test('Cannot reset with invalid token', async ({ page }) => {
await page.goto('/reset/user_id/token')
await expect(page.getByText('The provided link is invalid or has expired.')).toBeVisible()
})

test('Shows validation error if passwords do not match', async ({ page }) => {
await page.goto('/reset/e2e_test_user/e2e_test_token')
await page.fill('[data-attr="password"]', VALID_PASSWORD)
await expect(page.locator('.LemonProgress__track')).toBeVisible()
await page.fill('[data-attr="password-confirm"]', '1234567A')
await page.click('button[type=submit]')
await expect(page.getByText('Passwords do not match')).toBeVisible()
await expect(page).toHaveURL('/reset/e2e_test_user/e2e_test_token')
})

test('Shows validation error if password is too short', async ({ page }) => {
await page.goto('/reset/e2e_test_user/e2e_test_token')
await page.fill('[data-attr="password"]', '123')
await page.fill('[data-attr="password-confirm"]', '123')
await page.click('button[type=submit]')
const passwordTooShortError = page.getByText('Must be at least 8 characters long')
await expect(passwordTooShortError).toBeVisible()
await expect(passwordTooShortError).toHaveClass(/text-danger/)
await expect(page).toHaveURL('/reset/e2e_test_user/e2e_test_token')
})

test('Can reset password with valid token', async ({ page }) => {
await page.goto('/reset/e2e_test_user/e2e_test_token')
await page.fill('[data-attr="password"]', VALID_PASSWORD)
Expand Down
32 changes: 0 additions & 32 deletions playwright/e2e/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ test.describe('Auth', () => {
loginPage = new LoginPage(page)
})

test('Logout', async ({ page }) => {
await page.locator('[data-attr=new-account-menu-logout-button]').click()
await expect(page).toHaveURL('/login')
})

test('Logout and login', async ({ page }) => {
await page.locator('[data-attr=new-account-menu-logout-button]').click()

Expand Down Expand Up @@ -100,20 +95,6 @@ test.describe('Auth', () => {
await expect(page).toHaveURL(/\/project\/\d+/)
})

test('Redirect to appropriate place after login', async ({ page, context }) => {
await context.clearCookies()
await page.goto('/activity/events', { waitUntil: 'commit' })
await expect(page).toHaveURL(/\/login/)

await loginPage.enterUsername(LOGIN_USERNAME)
await loginPage.continueWithEmail()

await loginPage.enterPassword(LOGIN_PASSWORD)
await loginPage.clickLogin()

await expect(page).toHaveURL(/\/activity\/events/)
})

test('Redirect to appropriate place after login with complex URL', async ({ page, context }) => {
await context.clearCookies()
await page.goto('/insights?search=testString', { waitUntil: 'commit' })
Expand Down Expand Up @@ -151,17 +132,4 @@ test.describe('Auth', () => {
await page.goto('/signup')
await expect(page).toHaveURL(/\/project\/\d+/)
})

test('Logout in another tab results in logout in the current tab too', async ({ page, context }) => {
const secondPage = await context.newPage()
await secondPage.goto('/')
await secondPage.locator('[data-attr=new-account-menu-button]').waitFor({ state: 'visible', timeout: 30000 })
await secondPage.locator('[data-attr=new-account-menu-button]').click()
await secondPage.locator('[data-attr=new-account-menu-logout-button]').click()
await secondPage.waitForURL(/\/login/)

// Reload the page to trigger API calls that will detect the logout
await page.reload()
await page.waitForURL(/\/login/, { timeout: 30000 })
})
})
78 changes: 5 additions & 73 deletions playwright/e2e/signup.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import { randomString } from '@playwright-utils'
import type { APIRequestContext, Page } from '@playwright/test'
import type { Page } from '@playwright/test'

import { expect, test } from '../utils/workspace-test-base'

const VALID_PASSWORD = 'hedgE-hog-123%'

const ensureExistingUser = async (request: APIRequestContext, email: string): Promise<void> => {
await request.post('/api/signup/', {
data: {
first_name: 'Test',
email,
password: VALID_PASSWORD,
organization_name: 'Hogflix',
role_at_organization: 'Engineering',
},
})
}

const startSignupFlow = async (page: Page, email: string, password: string): Promise<void> => {
await page.locator('[data-attr=signup-email]').fill(email)
await expect(page.locator('[data-attr=signup-email]')).toHaveValue(email)
Expand Down Expand Up @@ -55,18 +43,14 @@ test.describe('Signup', () => {
await page.goto('/signup')
})

test('An existing email goes to the login step', async ({ page, request }) => {
await ensureExistingUser(request, 'test@posthog.com')
await submitEmailAndExpectLoginStep(page, 'test@posthog.com')
})

test('Can create user account with first name, last name and organization name', async ({ page }) => {
let signupRequestBody: string | null = null

await page.route('/api/signup/', async (route) => {
signupRequestBody = route.request().postData()
await route.continue()
})
await page.goto('/signup?next=/custom_path')

const email = `${randomString('new_user')}@posthog.com`
await startSignupFlow(page, email, VALID_PASSWORD)
Expand All @@ -77,14 +61,16 @@ test.describe('Signup', () => {
await page.locator('[data-attr=signup-role-at-organization]').click()
await page.locator('.Popover li').filter({ hasText: 'Engineering' }).click()
await expect(page.locator('[data-attr=signup-role-at-organization]')).toContainText('Engineering')
const signupPromise = page.waitForResponse('/api/signup/')
await page.locator('[data-attr=signup-submit]').click()
await signupPromise

const parsedBody = JSON.parse(signupRequestBody!)
expect(parsedBody.first_name).toEqual('Alice')
expect(parsedBody.last_name).toEqual('Bob')
expect(parsedBody.organization_name).toEqual('Hogflix SpinOff')

await expect(page).toHaveURL(/\/verify_email\/[a-zA-Z0-9_.-]*/)
await expect(page).toHaveURL(/\/verify_email\/[a-zA-Z0-9_.-]*\?next=(\/|%2F)custom_path/)
Comment thread
gantoine marked this conversation as resolved.
Resolved
})

test('Can submit the signup form multiple times if there is a generic email set', async ({ page }) => {
Expand Down Expand Up @@ -157,58 +143,4 @@ test.describe('Signup', () => {
'Inactive social login session.'
)
})

test('Shows redirect notice if redirecting for maintenance', async ({ page }) => {
// Equivalent to setupFeatureFlags in Playwright
await page.route('**/flags/*', async (route) => {
const response = {
config: {
enable_collect_everything: true,
},
featureFlags: {
'redirect-signups-to-instance': 'us',
},
isAuthenticated: false,
}
await route.fulfill({ json: response })
})

await page.context().clearCookies()
await page.goto('/')
await expect(page).toHaveURL(/.*\/login/)

// Modify window object before page load
await page.goto('/signup?maintenanceRedirect=true')

// Inject cloud = true into the context
await page.evaluate(() => {
const context = window['POSTHOG_APP_CONTEXT']
if (context && context.preflight) {
context.preflight.cloud = true
}
})

await expect(page.locator('[data-attr="info-toast"]')).toContainText(
`You've been redirected to signup on our US instance while we perform maintenance on our other instance.`
)
})

test('Preserves next parameter through signup flow', async ({ page }) => {
// Start signup with next parameter
await page.goto('/signup?next=/custom_path')

const email = `${randomString('new_user')}@posthog.com`
await startSignupFlow(page, email, VALID_PASSWORD)
await page.locator('[data-attr=signup-name]').fill('Alice Bob')
await expect(page.locator('[data-attr=signup-name]')).toHaveValue('Alice Bob')
await page.locator('[data-attr=signup-organization-name]').fill('Hogflix SpinOff')
await expect(page.locator('[data-attr=signup-organization-name]')).toHaveValue('Hogflix SpinOff')
await page.locator('[data-attr=signup-role-at-organization]').click()
await page.locator('.Popover li').filter({ hasText: 'Engineering' }).click()
await expect(page.locator('[data-attr=signup-role-at-organization]')).toContainText('Engineering')
await page.locator('[data-attr=signup-submit]').click()

// Verify we're redirected to verify_email page with next parameter preserved
await expect(page).toHaveURL(/\/verify_email\/[a-zA-Z0-9_.-]*\?next=(\/|%2F)custom_path/)
})
})
38 changes: 1 addition & 37 deletions playwright/e2e/sql-editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ test.describe('SQL Editor', () => {
await goToSqlEditor(page)
})

test('See SQL Editor', async ({ page }) => {
await expect(page.getByTestId('editor-scene')).toBeVisible()
await expect(page.getByTestId('menu-item-sources')).toBeVisible()
await expect(page.getByTestId('sql-editor-add-source')).toBeVisible()
await expect(page.getByTestId('sql-editor-output-pane-empty-state')).toBeVisible()
await expect(page.locator('.scene-name h1 span').getByText('New SQL query', { exact: true })).toBeVisible()
})

test('Add source link', async ({ page }) => {
// Add source opens the new-source page in a new browser tab
const popupPromise = page.context().waitForEvent('page')
Expand All @@ -71,37 +63,9 @@ test.describe('SQL Editor', () => {
await expect(newSourceTab).toHaveURL(/.*\/data-warehouse\/new-source/)
})

test('Run query', async ({ page }) => {
test('Run, save and materialize a view', async ({ page }) => {
await expect(page.getByTestId('sql-editor-output-pane-empty-state')).toBeVisible()
await runQueryAndWaitForResults(page)
})

test('Save view', async ({ page }) => {
await runQueryAndWaitForResults(page)
await dismissQuickStart(page)

await expect(page.getByTestId('sql-editor-save-options-button')).toBeEnabled()
await page.getByTestId('sql-editor-save-options-button').click()
await page.getByText('Save as view', { exact: true }).click()

const nameInput = page.getByTestId('sql-editor-input-save-view-name')
const uniqueViewName = `test_view_${test.info().repeatEachIndex}_${Date.now()}`
const submitButton = page.getByRole('button', { name: 'Submit' })
const saveViewModal = page.getByRole('dialog', { name: 'Save as view' })

await expect(nameInput).toBeVisible()
await nameInput.fill(uniqueViewName)
await expect(submitButton).toBeEnabled()
await submitButton.click()

await expect(saveViewModal).not.toBeVisible({ timeout: 60000 })
await expect(page.locator('.scene-name h1 span').getByText(uniqueViewName, { exact: true })).toBeVisible({
timeout: 60000,
})
})

test('Materialize view pane', async ({ page }) => {
await runQueryAndWaitForResults(page)
await dismissQuickStart(page)

await expect(page.getByTestId('sql-editor-save-options-button')).toBeEnabled()
Expand Down
12 changes: 0 additions & 12 deletions playwright/e2e/toolbar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,4 @@ test.describe('Toolbar', () => {

await expect(page.locator('#__POSTHOG_TOOLBAR__ .Toolbar')).toBeVisible({ timeout: 5000 })
})

test('Toolbar item in sidebar has launch options', async ({ page }) => {
await page.goToMenuItem('toolbar')
await page.getByText('Add authorized URL').click()
await expect(page).toHaveURL(/.*\/toolbar/)
})

test('Can open add authorized URL form', async ({ page }) => {
await page.goToMenuItem('toolbar')
await page.locator('[data-attr="toolbar-add-url"]').click()
await expect(page.locator('[data-attr="url-input"]')).toBeVisible()
})
})
Loading