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
6 changes: 6 additions & 0 deletions .github/workflows/elixir.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ jobs:
- name: Install E2E dependencies
run: npm --prefix ./e2e ci

- name: Check format
run: npm --prefix ./e2e run check-format

- name: Check types
run: npm --prefix ./e2e run typecheck

Comment on lines +212 to +217
Copy link
Copy Markdown
Contributor Author

@apata apata Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runs once in every shard, so it's a bit wasteful, but I really want these to be enforced

- name: Install E2E Playwright Browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: ./e2e
Expand Down
4 changes: 2 additions & 2 deletions e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"scripts": {
"lint": "eslint",
"check-format": "prettier --check \"**/*.{js,mjs,ts,html,json,md}\"",
"test": "npx playwright test",
"test:ui": "npx playwright test --ui"
"typecheck": "tsc --noEmit --pretty",
"test": "npx playwright test"
},
"license": "MIT",
"devDependencies": {
Expand Down
26 changes: 14 additions & 12 deletions e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { defineConfig, devices } from '@playwright/test'

const baseURL: string = process.env.BASE_URL!
const isCI: boolean = !!process.env.CI

/**
* See https://playwright.dev/docs/test-configuration.
*/
Expand All @@ -8,23 +11,23 @@ export default defineConfig({
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
forbidOnly: isCI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
retries: isCI ? 2 : 0,
/* Make test timeout shorter when running tests in local dev env. */
timeout: process.env.CI ? 30_000 : 10_000,
timeout: isCI ? 30_000 : 10_000,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
baseURL: process.env.BASE_URL,
baseURL,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry'
},
/* Opt out of parallel tests on CI. */
...(isCI && { workers: 1 }),

/* Configure projects for major browsers */
projects: [
Expand Down Expand Up @@ -56,19 +59,18 @@ export default defineConfig({
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
cwd: '..',
command: 'MIX_ENV=e2e_test mix phx.server',
url: process.env.BASE_URL,
reuseExistingServer: !process.env.CI
command: 'mix phx.server',
env: { ...(process.env as Record<string, string>), MIX_ENV: 'e2e_test' },
gracefulShutdown: { signal: 'SIGTERM', timeout: 500 },
url: `${baseURL}/api/system/health/ready`,
reuseExistingServer: !isCI
}
})
8 changes: 4 additions & 4 deletions e2e/tests/dashboard/behaviours.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test'
import { test, expect, Page } from '@playwright/test'
import {
setupSite,
populateStats,
Expand All @@ -7,7 +7,7 @@ import {
addScrollDepthGoal,
addAllCustomProps,
addFunnel
} from '../fixtures.ts'
} from '../fixtures'
import {
tabButton,
expectHeaders,
Expand All @@ -19,9 +19,9 @@ import {
modal,
closeModalButton,
searchInput
} from '../test-utils.ts'
} from '../test-utils'

const getReport = (page) => page.getByTestId('report-behaviours')
const getReport = (page: Page) => page.getByTestId('report-behaviours')

test('goals breakdown', async ({ page, request }) => {
const report = getReport(page)
Expand Down
14 changes: 7 additions & 7 deletions e2e/tests/dashboard/breakdowns.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { setupSite, populateStats, addCustomGoal } from '../fixtures.ts'
import { setupSite, populateStats, addCustomGoal } from '../fixtures'
import {
tabButton,
expectHeaders,
Expand All @@ -12,7 +12,7 @@ import {
closeModalButton,
header,
searchInput
} from '../test-utils.ts'
} from '../test-utils'

test('sources breakdown', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -507,10 +507,10 @@ test('pages breakdown modal', async ({ page, request }) => {

// We generate <pagesCount> unique page entries, each with a different number of visits
const pageEvents = Array(pagesCount)
.fill()
.fill(null)
.map((_, idx) => {
return Array(idx + 1)
.fill()
.fill(null)
.map(() => {
return { name: 'pageview', pathname: `/page${idx + 1}/foo` }
})
Expand Down Expand Up @@ -548,7 +548,7 @@ test('pages breakdown modal', async ({ page, request }) => {

await test.step('displays 100 entries on a single page', async () => {
const pageRows = Array(100)
.fill()
.fill(null)
.map((_, idx) => {
return `/page${pagesCount - idx}/foo`
})
Expand Down Expand Up @@ -601,7 +601,7 @@ test('pages breakdown modal', async ({ page, request }) => {
await header(modal(page), 'Visitors').click()

const pageRows = Array(100)
.fill()
.fill(null)
.map((_, idx) => {
return `/page${idx + 1}/foo`
})
Expand Down Expand Up @@ -641,7 +641,7 @@ test('pages breakdown modal', async ({ page, request }) => {
await expect(searchInput(modal(page))).toHaveValue('')

const pageRows = Array(100)
.fill()
.fill(null)
.map((_, idx) => {
return `/page${idx + 1}/foo`
})
Expand Down
29 changes: 16 additions & 13 deletions e2e/tests/dashboard/filtering.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { setupSite, populateStats, addPageviewGoal } from '../fixtures.ts'
import { test, expect, Page } from '@playwright/test'
import { setupSite, populateStats, addPageviewGoal } from '../fixtures'
import {
filterButton,
filterItemButton,
Expand All @@ -8,10 +8,10 @@ import {
suggestedItem,
filterOperator,
filterOperatorOption
} from '../test-utils.ts'
} from '../test-utils'

test.describe('page filtering tests', () => {
const pageFilterButton = (page) => filterItemButton(page, 'Page')
const pageFilterButton = (page: Page) => filterItemButton(page, 'Page')

test('filtering by page with detailed behavior test', async ({
page,
Expand Down Expand Up @@ -258,7 +258,8 @@ test.describe('page filtering tests', () => {
})

test.describe('hostname filtering tests', () => {
const hostnameFilterButton = (page) => filterItemButton(page, 'Hostname')
const hostnameFilterButton = (page: Page) =>
filterItemButton(page, 'Hostname')

test('filtering by hostname', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -294,7 +295,7 @@ test.describe('hostname filtering tests', () => {
})

test.describe('acquisition filtering tests', () => {
const sourceFilterButton = (page) => filterItemButton(page, 'Source')
const sourceFilterButton = (page: Page) => filterItemButton(page, 'Source')

test('filtering by source information', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -397,7 +398,7 @@ test.describe('acquisition filtering tests', () => {
})
})

const utmTagsFilterButton = (page) => filterItemButton(page, 'UTM tags')
const utmTagsFilterButton = (page: Page) => filterItemButton(page, 'UTM tags')

test('filtering by UTM tags', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -554,7 +555,8 @@ test.describe('acquisition filtering tests', () => {
})

test.describe('location filtering tests', () => {
const locationFilterButton = (page) => filterItemButton(page, 'Location')
const locationFilterButton = (page: Page) =>
filterItemButton(page, 'Location')

test('filtering by location', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -641,7 +643,8 @@ test.describe('location filtering tests', () => {
})

test.describe('screen size filtering tests', () => {
const screenSizeFilterButton = (page) => filterItemButton(page, 'Screen size')
const screenSizeFilterButton = (page: Page) =>
filterItemButton(page, 'Screen size')

test('filtering by screen size', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -681,7 +684,7 @@ test.describe('screen size filtering tests', () => {
})

test.describe('browser filtering tests', () => {
const browserFilterButton = (page) => filterItemButton(page, 'Browser')
const browserFilterButton = (page: Page) => filterItemButton(page, 'Browser')

test('filtering by browser', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -743,7 +746,7 @@ test.describe('browser filtering tests', () => {
})

test.describe('operating system filtering tests', () => {
const operatingSystemFilterButton = (page) =>
const operatingSystemFilterButton = (page: Page) =>
filterItemButton(page, 'Operating system')

test('filtering by operating system', async ({ page, request }) => {
Expand Down Expand Up @@ -820,7 +823,7 @@ test.describe('operating system filtering tests', () => {
})

test.describe('goal filtering tests', () => {
const goalFilterButton = (page) => filterItemButton(page, 'Goal')
const goalFilterButton = (page: Page) => filterItemButton(page, 'Goal')

test('filtering by goals', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down Expand Up @@ -889,7 +892,7 @@ test.describe('goal filtering tests', () => {
})

test.describe('property filtering tests', () => {
const propFilterButton = (page) =>
const propFilterButton = (page: Page) =>
page.getByTestId('filtermenu').getByRole('link', { name: 'Property' })

test('filtering by properties', async ({ page, request }) => {
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/dashboard/general.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
makeSitePublic,
populateStats,
createSharedLink
} from '../fixtures.ts'
} from '../fixtures'

test('dashboard renders for logged in user', async ({ page, request }) => {
const { domain } = await setupSite({ page, request })
Expand Down
31 changes: 17 additions & 14 deletions e2e/tests/dashboard/segments.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { test, expect } from '@playwright/test'
import { setupSite, populateStats } from '../fixtures.ts'
import { test, expect, APIRequestContext, Page } from '@playwright/test'
import { setupSite, populateStats } from '../fixtures'
import {
filterButton,
filterItemButton,
applyFilterButton,
filterRow,
suggestedItem,
modal
} from '../test-utils.ts'
} from '../test-utils'

const setupSiteAndStats = async ({ page, request }) => {
const setupSiteAndStats = async ({
page,
request
}: {
page: Page
request: APIRequestContext
}) => {
const context = await setupSite({ page, request })

await populateStats({
Expand All @@ -25,12 +31,12 @@ const setupSiteAndStats = async ({ page, request }) => {
return context
}

const segmentMenu = (page) => page.getByTestId('segmentmenu')
const segmentMenu = (page: Page) => page.getByTestId('segmentmenu')

const sourceFilterButton = (page) => filterItemButton(page, 'Source')
const utmTagsFilterButton = (page) => filterItemButton(page, 'UTM tags')
const sourceFilterButton = (page: Page) => filterItemButton(page, 'Source')
const utmTagsFilterButton = (page: Page) => filterItemButton(page, 'UTM tags')

const addSourceFilter = async (page, sourceLabel) => {
const addSourceFilter = async (page: Page, sourceLabel: string) => {
const sourceFilterRow = filterRow(page, 'source')
const sourceInput = page.getByPlaceholder('Select a Source')

Expand All @@ -46,7 +52,7 @@ const addSourceFilter = async (page, sourceLabel) => {
await expect(page).toHaveURL(url)
}

const addUtmSourceFilter = async (page, utmSource) => {
const addUtmSourceFilter = async (page: Page, utmSource: string) => {
const utmSourceFilterRow = filterRow(page, 'utm_source')
const utmSourceInput = page.getByPlaceholder('Select a UTM Source')

Expand All @@ -62,7 +68,7 @@ const addUtmSourceFilter = async (page, utmSource) => {
await expect(page).toHaveURL(url)
}

const createPersonalSegment = async (page, name) => {
const createPersonalSegment = async (page: Page, name: string) => {
await page.getByRole('button', { name: 'See actions' }).click()

await page.getByRole('link', { name: 'Save as segment' }).click()
Expand Down Expand Up @@ -368,10 +374,7 @@ test('deleting segment', async ({ page, request }) => {
await expect(filterItemButton(page, 'Traffic from Google')).toBeHidden()
})

test('closing edited segment without saving', async ({
page,
request
}) => {
test('closing edited segment without saving', async ({ page, request }) => {
const { domain } = await setupSiteAndStats({ page, request })

await page.goto('/' + domain)
Expand Down
8 changes: 4 additions & 4 deletions e2e/tests/dashboard/top-stats.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
DateTimeFormatter
} from '@js-joda/core'
import { Locale } from '@js-joda/locale'
import { setupSite, populateStats } from '../fixtures.ts'
import { setupSite, populateStats, StatsEntry } from '../fixtures'

function currentTime(): ZonedDateTime {
return ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)
Expand Down Expand Up @@ -64,12 +64,12 @@ test('site switcher allows switching between different sites', async ({
await expect(page).toHaveURL(`/${domain2}`)
await expect(switcherButton).toHaveText(domain2)

await page.keyboard.press(domain3Key)
await page.keyboard.press(domain3Key!)

await expect(page).toHaveURL(`/${domain3}`)
await expect(switcherButton).toHaveText(domain3)

await page.keyboard.press(domain1Key)
await page.keyboard.press(domain1Key!)

await expect(page).toHaveURL(`/${domain1}`)
await expect(switcherButton).toHaveText(domain1)
Expand Down Expand Up @@ -180,7 +180,7 @@ test('different time ranges are supported', async ({ page, request }) => {
now.minusDays(720)
]

const events = []
const events: StatsEntry[] = []

eventTimes.forEach((ts, idx) => {
expectedCounts.forEach((expected) => {
Expand Down
Loading
Loading