| import type { APIRequestContext, Page } from '@playwright/test' |
| import { expect } from '@playwright/test' |
| import { expectLiveViewConnected, randomID } from './test-utils' |
|
|
| type User = { |
| name: string |
| email: string |
| password: string |
| } |
|
|
| type EventTimestamp = |
| | { minutesAgo: number } |
| | { hoursAgo: number } |
| | { daysAgo: number } |
| | string |
|
|
| type EventDate = { daysAgo: number } | string |
|
|
| type Event = { |
| type?: 'event' |
| name: string |
| user_id?: number |
| scroll_depth?: number |
| engagement_time?: number |
| revenue_reporting_amount?: string |
| revenue_reporting_currency?: string |
| pathname?: string |
| hostname?: string |
| country_code?: string |
| subdivision1_code?: string |
| city_geoname_id?: number |
| referrer_source?: string |
| referrer?: string |
| utm_medium?: string |
| utm_source?: string |
| utm_campaign?: string |
| utm_term?: string |
| utm_content?: string |
| click_id_param?: string |
| screen_size?: string |
| browser?: string |
| browser_version?: string |
| operating_system?: string |
| operating_system_version?: string |
| 'meta.key'?: string[] |
| 'meta.value'?: string[] |
| timestamp?: EventTimestamp |
| } |
|
|
| type Goal = { |
| event_name?: string |
| page_path?: string |
| scroll_threshold?: number |
| display_name?: string |
| currency?: string |
| } |
|
|
| type ImportedVisitors = { |
| type: 'imported_visitors' |
| visitors?: number |
| pageviews?: number |
| bounces?: number |
| visits?: number |
| visit_duration?: number |
| date?: EventDate |
| } |
|
|
| export type StatsEntry = Event | ImportedVisitors |
|
|
| export async function register({ |
| page, |
| request, |
| user |
| }: { |
| page: Page |
| request: APIRequestContext |
| user: User |
| }) { |
| await page.goto('/register', { waitUntil: 'commit' }) |
|
|
| await expectLiveViewConnected(page) |
|
|
| await expect( |
| page.getByRole('button', { name: 'Start my free trial' }) |
| ).toBeVisible() |
|
|
| await page.getByLabel('Full name').fill(user.name) |
| await page.getByLabel('Email').fill(user.email) |
| await page.getByLabel('Password', { exact: true }).fill(user.password) |
| await expect( |
| page.getByRole('button', { name: 'Start my free trial' }) |
| ).toBeEnabled() |
| await page.getByRole('button', { name: 'Start my free trial' }).click() |
|
|
| await expect( |
| page.getByRole('heading', { name: 'Check your email' }) |
| ).toBeVisible() |
|
|
| const response = await request.get('/sent-emails-api/emails.json') |
|
|
| const emailData: Array<{ to: string[][]; subject: string }> = |
| await response.json() |
|
|
| const emails = emailData.filter( |
| (e) => |
| e.to![0]![0] === user.name && |
| e.subject.indexOf('is your Plausible email verification code') > -1 |
| ) |
|
|
| expect(emails.length).toEqual(1) |
|
|
| const [code] = emails[0]!.subject.split(' ') |
|
|
| await page.locator('input[name=code]').fill(code!) |
|
|
| await page.getByRole('button', { name: 'Activate' }).click() |
|
|
| await expect( |
| page.getByRole('button', { name: 'Install Plausible' }) |
| ).toBeVisible() |
| } |
|
|
| export async function login({ page, user }: { page: Page; user: User }) { |
| await page.goto('/login', { waitUntil: 'commit' }) |
|
|
| await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible() |
|
|
| await page.getByLabel('Email').fill(user.email) |
| await page.getByLabel('Password').fill(user.password) |
| await page.getByRole('button', { name: 'Sign in' }).click() |
|
|
| await expect(page.getByRole('button', { name: user.name })).toBeVisible() |
| } |
|
|
| export async function logout(page: Page) { |
| await page.goto('/logout', { waitUntil: 'commit' }) |
|
|
| await expect( |
| page.getByRole('heading', { name: 'Welcome to Plausible!' }) |
| ).toBeVisible() |
| } |
|
|
| export async function addSite({ |
| page, |
| domain |
| }: { |
| page: Page |
| domain: string |
| }) { |
| await page.goto('/sites/new', { waitUntil: 'commit' }) |
|
|
| await expect( |
| page.getByRole('button', { name: 'Install Plausible' }) |
| ).toBeVisible() |
|
|
| await page.getByLabel('Domain').fill(domain) |
| await page.getByLabel('Reporting timezone').selectOption('Etc/UTC') |
|
|
| await page.getByRole('button', { name: 'Install Plausible' }).click() |
|
|
| await expect(page).toHaveURL(/\/installation/) |
| } |
|
|
| export async function makeSitePublic({ |
| page, |
| domain |
| }: { |
| page: Page |
| domain: string |
| }) { |
| await page.goto(`/${domain}/settings/visibility`, { waitUntil: 'commit' }) |
|
|
| await page |
| .getByRole('form', { name: 'Make stats publicly available' }) |
| .getByRole('button') |
| .click() |
|
|
| await expect(page.locator('body')).toContainText('are now public') |
| } |
|
|
| export async function createSharedLink({ |
| page, |
| domain, |
| name, |
| password |
| }: { |
| page: Page |
| domain: string |
| name: string |
| password?: string |
| }): Promise<string> { |
| const modal = page.locator('#shared-links-form-modal') |
| const table = page.locator('#shared-links-table') |
|
|
| await page.goto(`/${domain}/settings/visibility`, { waitUntil: 'commit' }) |
|
|
| await page.getByRole('button', { name: 'Add shared link' }).click() |
|
|
| await modal.getByLabel('Name').fill(name) |
|
|
| if (password) { |
| await modal.locator('button#password-protect-').click() |
|
|
| await modal.locator('input#shared_link_password').fill(password) |
| } |
|
|
| await page.getByRole('button', { name: 'Create shared link' }).click() |
|
|
| await expect(page.locator('body')).toContainText('Shared link saved') |
|
|
| const link = await table |
| .locator('tr') |
| .filter({ hasText: name }) |
| .locator('input') |
| .inputValue() |
|
|
| return link |
| } |
|
|
| export async function populateStats({ |
| request, |
| domain, |
| events |
| }: { |
| request: APIRequestContext |
| domain: string |
| events: StatsEntry[] |
| }) { |
| const response = await request.post('/e2e-tests/stats', { |
| headers: { |
| 'Content-Type': 'application/json', |
| Accept: 'application/json' |
| }, |
| data: { domain: domain, events: events } |
| }) |
|
|
| expect(response.ok()).toBeTruthy() |
| } |
|
|
| export async function addGoal({ |
| request, |
| domain, |
| params |
| }: { |
| request: APIRequestContext |
| domain: string |
| params: Goal |
| }) { |
| const response = await request.post('/e2e-tests/goal', { |
| headers: { |
| 'Content-Type': 'application/json', |
| Accept: 'application/json' |
| }, |
| data: { domain: domain, ...params } |
| }) |
|
|
| expect(response.ok()).toBeTruthy() |
| } |
|
|
| export async function addCustomGoal({ |
| page, |
| domain, |
| name, |
| displayName, |
| currency, |
| // Useful when adding a goal for which there's no matching stat yet |
| clickManually = true |
| }: { |
| page: Page |
| domain: string |
| name: string |
| displayName?: string |
| currency?: string |
| clickManually?: boolean |
| }) { |
| await page.goto(`/${domain}/settings/goals`, { waitUntil: 'commit' }) |
| |
| await expectLiveViewConnected(page) |
| |
| await page.getByRole('button', { name: 'Add goal' }).click() |
| const customEventButton = page.locator( |
| 'button[phx-value-goal-type="custom_events"]' |
| ) |
| |
| await customEventButton.click() |
| |
| if (clickManually) { |
| await page.getByRole('button', { name: 'Add manually' }).click() |
| } |
| |
| await expect( |
| page.getByRole('heading', { name: `Add goal for ${domain}` }) |
| ).toBeVisible() |
| // NOTE: Locating inputs by role and label does not work in this case |
| // for some reason. |
| const nameInput = page.locator('input[placeholder="e.g. Signup"]') |
| await nameInput.fill(name) |
| await page.locator(`a[data-display-value="${name}"]`).click() |
| await expect(nameInput).toHaveAttribute('value', name) |
| |
| if (displayName) { |
| await page |
| .locator('input#custom_event_display_name_input') |
| .fill(displayName) |
| } |
| |
| if (currency) { |
| await page |
| .locator('button[aria-labelledby="enable-revenue-tracking"]') |
| .click() |
| const currencyInput = page.locator('input[id^=currency_input_]') |
| await currencyInput.fill(currency) |
| await page.locator(`a[phx-value-submit-value="${currency}"]`).click() |
| await expect(page.locator('input[name="goal[currency]"]')).toHaveAttribute( |
| 'value', |
| currency |
| ) |
| } |
| |
| await page |
| .locator('form[phx-submit="save-goal"]') |
| .getByRole('button', { name: 'Add goal' }) |
| .click() |
| |
| await expect(page.locator('body')).toContainText('Goal saved successfully') |
| } |
| |
| export async function addPageviewGoal({ |
| page, |
| domain, |
| pathname, |
| displayName |
| }: { |
| page: Page |
| domain: string |
| pathname: string |
| displayName?: string |
| }) { |
| await page.goto(`/${domain}/settings/goals`, { waitUntil: 'commit' }) |
| |
| await expectLiveViewConnected(page) |
| |
| await page.getByRole('button', { name: 'Add goal' }).click() |
| const pageviewEventButton = page.locator( |
| 'button[phx-value-goal-type="pageviews"]' |
| ) |
| |
| await pageviewEventButton.click() |
| |
| await expect( |
| page.getByRole('heading', { name: `Add goal for ${domain}` }) |
| ).toBeVisible() |
| |
| const pathnameInput = page.locator('input[id^="page_path_input"]') |
| await pathnameInput.fill(pathname) |
| await page.locator(`a[data-display-value="${pathname}"]`).click() |
| await expect(pathnameInput).toHaveAttribute('value', pathname) |
| if (displayName) { |
| await page.locator('input#pageview_display_name_input').fill(displayName) |
| } |
| |
| await page |
| .locator('form[phx-submit="save-goal"]') |
| .getByRole('button', { name: 'Add goal' }) |
| .click() |
| |
| await expect(page.locator('body')).toContainText('Goal saved successfully') |
| } |
| |
| export async function addScrollDepthGoal({ |
| page, |
| domain, |
| pathname, |
| displayName, |
| scrollPercentage |
| }: { |
| page: Page |
| domain: string |
| pathname: string |
| displayName?: string |
| scrollPercentage?: number |
| }) { |
| await page.goto(`/${domain}/settings/goals`, { waitUntil: 'commit' }) |
| |
| await expectLiveViewConnected(page) |
| |
| await page.getByRole('button', { name: 'Add goal' }).click() |
| const scrollDepthEventButton = page.locator( |
| 'button[phx-value-goal-type="scroll"]' |
| ) |
| |
| await scrollDepthEventButton.click() |
| |
| await expect( |
| page.getByRole('heading', { name: `Add goal for ${domain}` }) |
| ).toBeVisible() |
| |
| if (scrollPercentage) { |
| await page |
| .locator('input[name="goal[scroll_threshold]"]') |
| .fill(scrollPercentage.toString()) |
| } |
| |
| const pathnameInput = page.locator('input[id^="scroll_page_path_input"]') |
| await pathnameInput.fill(pathname) |
| await page.locator(`a[data-display-value="${pathname}"]`).click() |
| await expect(pathnameInput).toHaveAttribute('value', pathname) |
| |
| if (displayName) { |
| await page.locator('input#scroll_display_name_input').fill(displayName) |
| } |
| |
| await page |
| .locator('form[phx-submit="save-goal"]') |
| .getByRole('button', { name: 'Add goal' }) |
| .click() |
| |
| await expect(page.locator('body')).toContainText('Goal saved successfully') |
| } |
| |
| export async function addCustomProp({ |
| page, |
| domain, |
| name |
| }: { |
| page: Page |
| domain: string |
| name: string |
| }) { |
| await page.goto(`/${domain}/settings/properties`, { waitUntil: 'commit' }) |
| |
| await expectLiveViewConnected(page) |
| |
| await page.getByRole('button', { name: 'Add property' }).click() |
| |
| await expect( |
| page.getByRole('heading', { name: `Add property for ${domain}` }) |
| ).toBeVisible() |
| |
| const propInput = page.locator('input#prop_input') |
| await propInput.fill(name) |
| await page.locator(`a[data-display-value="${name}"]`).click() |
| await expect(propInput).toHaveAttribute('value', name) |
| |
| await page |
| .locator('form[phx-submit="allow-prop"]') |
| .getByRole('button', { name: 'Add property' }) |
| .click() |
| |
| await expect(page.locator('body')).toContainText( |
| 'Property added successfully' |
| ) |
| } |
| |
| export async function addAllCustomProps({ |
| page, |
| domain |
| }: { |
| page: Page |
| domain: string |
| }) { |
| await page.goto(`/${domain}/settings/properties`, { waitUntil: 'commit' }) |
| |
| await expectLiveViewConnected(page) |
| |
| await page.getByRole('button', { name: 'Add property' }).click() |
| |
| await expect( |
| page.getByRole('heading', { name: `Add property for ${domain}` }) |
| ).toBeVisible() |
| |
| await page.getByText(/Click to add [0-9]+ existing properties/).click() |
| |
| await expect(page.locator('body')).toContainText( |
| 'Properties added successfully' |
| ) |
| } |
| |
| export async function addFunnel({ |
| request, |
| domain, |
| name, |
| steps |
| }: { |
| request: APIRequestContext |
| domain: string |
| name: string |
| steps: string[] |
| }) { |
| const response = await request.post('/e2e-tests/funnel', { |
| headers: { |
| 'Content-Type': 'application/json', |
| Accept: 'application/json' |
| }, |
| data: { domain: domain, name: name, steps: steps } |
| }) |
| |
| expect(response.ok()).toBeTruthy() |
| } |
| |
| export async function setupSite({ |
| user, |
| page, |
| request, |
| ...opts |
| }: { |
| user?: User |
| page: Page |
| request: APIRequestContext |
| domain?: string |
| }): Promise<{ domain: string; user: User }> { |
| const domain = opts.domain ?? `${randomID()}.example.com` |
| |
| if (!user) { |
| const userID = randomID() |
| |
| user = { |
| name: `User ${userID}`, |
| email: `email-${userID}@example.com`, |
| password: 'VeryStrongVerySecret' |
| } |
| |
| await register({ page, request, user }) |
| } |
| |
| await addSite({ page, domain }) |
| |
| return { domain, user } |
| } |
| |