diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a54fa0a36..8c70f9de7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ on: - Makefile - 'src/**' - 'spec/**' + - 'playwright.config.js' - 'shard.*' - 'static/**' - 'views/**' @@ -134,6 +135,60 @@ jobs: name: bin path: bin/ + path_filter: + name: Filters for fronted specs + runs-on: ubuntu-latest + outputs: + views: ${{ steps.changes.outputs.views }} + js: ${{ steps.changes.outputs.js }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + views: + - '**/*.ecr' + js: + - '**/*.js' + + spec_frontend: + name: Fronted specs + runs-on: ubuntu-latest + needs: [compile, path_filter] + if: ${{ needs.path_filter.outputs.views == 'true' || needs.path_filter.outputs.js == 'true' }} + steps: + - name: Install LavinMQ dependencies + run: | + sudo apt-get update + sudo apt-get install -y make libevent-2.1-7 + + - name: Download lavinmq + uses: actions/download-artifact@v3 + with: + name: bin + path: bin + + - name: Run LavinMQ in background + run: | + chmod +x bin/* + bin/lavinmq --data-dir=/tmp/amqp --bind=:: & + + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Install @playwright/test + run: npm install @playwright/test + + - name: Install browsers + run: npx playwright install --with-deps + + - name: Run tests + run: npx playwright test --reporter=list + java-client-test: name: RabbitMQ java client test runs-on: ubuntu-20.04 diff --git a/.gitignore b/.gitignore index 53d9c39e8c..7819407ead 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,7 @@ config.ini *.pem perf.data* massif.* +node_modules +package.json +package-lock.json +/playwright/ diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000000..2cb0598434 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,63 @@ +// @ts-check +import { defineConfig, devices, expect } from '@playwright/test' +import './spec/frontend/expect_extensions.js' + +/** + * @see https://playwright.dev/docs/test-configuration + */ +module.exports = defineConfig( + { + testDir: './spec/frontend', + /* 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, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* 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 ?? 'http://127.0.0.1:15672', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'setup', + testMatch: /.*\.setup\.js/ + }, + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'], + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + storageState: 'playwright/.auth/user.json' + }, + dependencies: ['setup'], + }, + ], + }) + diff --git a/spec/frontend/README.md b/spec/frontend/README.md new file mode 100644 index 0000000000..962e35e045 --- /dev/null +++ b/spec/frontend/README.md @@ -0,0 +1,3 @@ +# Frontend specs + +These specs are some frontend. diff --git a/spec/frontend/auth.setup.js b/spec/frontend/auth.setup.js new file mode 100644 index 0000000000..cae9c77885 --- /dev/null +++ b/spec/frontend/auth.setup.js @@ -0,0 +1,14 @@ +import { test, test as setup, expect } from '@playwright/test'; + +const authFile = 'playwright/.auth/user.json'; + + +setup('authenticate', async ({ page }) => { + await page.goto('/login'); + await page.getByLabel('Username').fill('guest'); + await page.getByLabel('Password').fill('guest'); + await page.getByRole('button').click(); + await page.waitForURL('/'); + await page.context().storageState({ path: authFile }); +}); + diff --git a/spec/frontend/auth.spec.js b/spec/frontend/auth.spec.js new file mode 100644 index 0000000000..a816209194 --- /dev/null +++ b/spec/frontend/auth.spec.js @@ -0,0 +1,11 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test.describe('when unauthenticated', _ => { + test.use({ storageState: {} }) + test('redirects to login', async ({ page }) => { + await page.goto('/'); + await expect(page).toHaveURL(/\/login$/); + }) +}) + diff --git a/spec/frontend/channel.spec.js b/spec/frontend/channel.spec.js new file mode 100644 index 0000000000..5091f5761f --- /dev/null +++ b/spec/frontend/channel.spec.js @@ -0,0 +1,11 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("channel", _ => { + test('is loaded', async ({ page, baseURL }) => { + const channelName = "127.0.0.1:63221[1]" + const apiChannelRequest = helpers.waitForPathRequest(page, `/api/channels/${channelName}`, {}) + await page.goto(`/channel#name=${channelName}`) + await expect(apiChannelRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/channels.spec.js b/spec/frontend/channels.spec.js new file mode 100644 index 0000000000..b1c39895b9 --- /dev/null +++ b/spec/frontend/channels.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("channels", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiChannelsRequest = helpers.waitForPathRequest(page, '/api/channels', {}) + await page.goto('/channels') + await expect(apiChannelsRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/connection.spec.js b/spec/frontend/connection.spec.js new file mode 100644 index 0000000000..c425d73b66 --- /dev/null +++ b/spec/frontend/connection.spec.js @@ -0,0 +1,17 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("connection", _ => { + test('info is loaded', async ({ page, baseURL }) => { + const connectionName = "127.0.0.1:63610 -> 127.0.0.1:12345" + const apiConnectionRequest = helpers.waitForPathRequest(page, `/api/connections/${connectionName}`, {}) + await page.goto(`/connection#name=${connectionName}`) + await expect(apiConnectionRequest).toBeRequested() + }) + test('channels are loaded', async ({ page, baseURL }) => { + const connectionName = "127.0.0.1:63610 -> 127.0.0.1:12345" + const apiChannelsRequest = helpers.waitForPathRequest(page, `/api/connections/${connectionName}/channels`, {}) + await page.goto(`/connection#name=${connectionName}`) + await expect(apiChannelsRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/connections.spec.js b/spec/frontend/connections.spec.js new file mode 100644 index 0000000000..992ceded7d --- /dev/null +++ b/spec/frontend/connections.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("connections", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiConnectionsRequest = helpers.waitForPathRequest(page, '/api/connections') + await page.goto('/connections') + await expect(apiConnectionsRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/exchanges.spec.js b/spec/frontend/exchanges.spec.js new file mode 100644 index 0000000000..76b84d7027 --- /dev/null +++ b/spec/frontend/exchanges.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("exchanges", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiExchangesRequest = helpers.waitForPathRequest(page, '/api/exchanges', {}) + await page.goto('/exchanges') + await expect(apiExchangesRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/expect_extensions.js b/spec/frontend/expect_extensions.js new file mode 100644 index 0000000000..b5095388a4 --- /dev/null +++ b/spec/frontend/expect_extensions.js @@ -0,0 +1,47 @@ +import { expect } from '@playwright/test'; + +expect.extend( + { + async toBeRequested(received, params) { + return received.then(reqs => { + return { + message: _ => 'requested', + pass: true + } + }).catch(e => { + return { + message: _ => e.message, + pass: false + } + }) + }, + + async toHaveQueryParams(received, query) { + try { + const request = await received + const requestedUrl = new URL(request.url()) + const expectedParams = new URLSearchParams(query) + const actualParams = requestedUrl.searchParams + for (let [name, value] of expectedParams.entries()) { + const actualValue = actualParams.get(name) + if (actualValue !== value) { + return { + message: _ => `expected query param '${name}' to be '${value}', got '${actualValue}'`, + pass: false + } + } + } + return { + message: _ => "yes", + pass: true + } + } catch(e) { + return { + message: _ => e.toString(), + pass: false + } + } + } + }) + + diff --git a/spec/frontend/federation.spec.js b/spec/frontend/federation.spec.js new file mode 100644 index 0000000000..fdeea519fb --- /dev/null +++ b/spec/frontend/federation.spec.js @@ -0,0 +1,12 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("federation", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiFederationUpstreamsRequest = helpers.waitForPathRequest(page, '/api/parameters/federation-upstream', {}) + const apiFederationLinksRequest = helpers.waitForPathRequest(page, '/api/federation-links', {}) + await page.goto('/federation') + await expect(apiFederationUpstreamsRequest).toBeRequested() + await expect(apiFederationLinksRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/fixtures.js b/spec/frontend/fixtures.js new file mode 100644 index 0000000000..55fc6c4195 --- /dev/null +++ b/spec/frontend/fixtures.js @@ -0,0 +1,14 @@ +import { test as base, expect } from "@playwright/test"; + +const vhosts = ['foo', 'bar'] +const test = base.extend( + { + vhosts, + page: async ({ baseURL, page }, use) => { + const vhostResponse = vhosts.map(x => { return {name: x} }) + await page.route(/\/api\/vhosts(\?|$)/, async route => await route.fulfill({ json: vhostResponse })) + use(page) + } + }) + +export { test, expect } diff --git a/spec/frontend/helpers.js b/spec/frontend/helpers.js new file mode 100644 index 0000000000..ba10bd501b --- /dev/null +++ b/spec/frontend/helpers.js @@ -0,0 +1,22 @@ +function waitForPathRequest(page, path, response_data = {}) { + const matchUrl = new URL(path, 'http://example.com') + return new Promise((resolve, reject) => { + const handler = (route, request) => { + const requestedUrl = new URL(request.url()) + if (decodeURIComponent(requestedUrl.pathname) !== decodeURIComponent(matchUrl.pathname)) { + return route.continue() + } + page.unroute('**/*', handler) + route.fulfill({ json: response_data }) + resolve(request) + } + page.route('**/*', handler) + }) + return page.waitForRequest(req => { + const reqUrl = new URL(req.url()) + return reqUrl.pathname == path + }, { timeout: 1000 }) +} + + +export { waitForPathRequest } diff --git a/spec/frontend/layout.spec.js b/spec/frontend/layout.spec.js new file mode 100644 index 0000000000..b1a84e4790 --- /dev/null +++ b/spec/frontend/layout.spec.js @@ -0,0 +1,17 @@ +import { test, expect } from './fixtures.js' + +test.describe("vhosts", _ => { + test('are loaded', async ({ page, baseURL, vhosts }) => { + await page.goto('/') + const vhostOptions = await page.locator('#userMenuVhost option').allTextContents() + vhosts.forEach(vhost => { + expect(vhostOptions).toContain(vhost) + }) + }) + + test('remember selection', async ({ page }) => { + await page.goto('/') + await page.locator('#userMenuVhost').selectOption('foo') // selectOption trigger page load + await expect(page.locator('#userMenuVhost option:checked')).toHaveText(['foo']) + }) +}) diff --git a/spec/frontend/logs.spec.js b/spec/frontend/logs.spec.js new file mode 100644 index 0000000000..7b0dcdb593 --- /dev/null +++ b/spec/frontend/logs.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("logs", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiLogsRequest = page.waitForRequest(/\/api\/livelog$/, { timeout: 1000 }) + await page.goto('/logs') + await expect(apiLogsRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/nodes.spec.js b/spec/frontend/nodes.spec.js new file mode 100644 index 0000000000..020e17e712 --- /dev/null +++ b/spec/frontend/nodes.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("nodes", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiNodesRequest = helpers.waitForPathRequest(page, '/api/nodes', {}) + await page.goto('/nodes') + await expect(apiNodesRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/operator-policies.spec.js b/spec/frontend/operator-policies.spec.js new file mode 100644 index 0000000000..7fadaac076 --- /dev/null +++ b/spec/frontend/operator-policies.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("operator-policies", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiPoliciesRequest = helpers.waitForPathRequest(page, '/api/operator-policies', {}) + await page.goto('/operator-policies') + await expect(apiPoliciesRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/overview.spec.js b/spec/frontend/overview.spec.js new file mode 100644 index 0000000000..fade3d86c8 --- /dev/null +++ b/spec/frontend/overview.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("overview", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiOverviewRequest = helpers.waitForPathRequest(page, '/api/overview') + await page.goto('/') + await expect(apiOverviewRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/policies.spec.js b/spec/frontend/policies.spec.js new file mode 100644 index 0000000000..e93465f832 --- /dev/null +++ b/spec/frontend/policies.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("policies", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiPoliciesRequest = helpers.waitForPathRequest(page, '/api/policies', {}) + await page.goto('/policies') + await expect(apiPoliciesRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/queue.spec.js b/spec/frontend/queue.spec.js new file mode 100644 index 0000000000..22f5aecd44 --- /dev/null +++ b/spec/frontend/queue.spec.js @@ -0,0 +1,19 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("queue", _ => { + test('info is loaded', async ({ page, baseURL }) => { + const vhost = "/" + const queueName = "foo" + const apiQueueRequest = helpers.waitForPathRequest(page, `/api/queues/${encodeURIComponent(vhost)}/${queueName}`, {}) + await page.goto(`/queue#vhost=${encodeURIComponent(vhost)}&name=${queueName}`) + await expect(apiQueueRequest).toBeRequested() + }) + test('bindings are loaded', async ({ page, baseURL }) => { + const vhost = "/" + const queueName = "foo" + const apiBindingsRequest = helpers.waitForPathRequest(page, `/api/queues/${encodeURIComponent(vhost)}/${queueName}/bindings`, {}) + await page.goto(`/queue#vhost=${encodeURIComponent(vhost)}&name=${queueName}`) + await expect(apiBindingsRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/queues.spec.js b/spec/frontend/queues.spec.js new file mode 100644 index 0000000000..bcf442ba15 --- /dev/null +++ b/spec/frontend/queues.spec.js @@ -0,0 +1,101 @@ +import * as helpers from './helpers.js' +import * as qHelpers from './queues_helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("queues", _ => { + // Test that different combination of hash params are sent in the request + test.describe('are loaded with params when hash params', _ => { + test('are empty', async ({ page, baseURL }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues') + await page.goto('/queues') + await expect(apiQueuesRequest).toBeRequested() + }) + + test('are page_size=10, page=2', async ({ page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues') + await page.goto('/queues#page_size=10&page=2') + await expect(apiQueuesRequest).toHaveQueryParams('page_size=10&page=2') + }) + + test('are name=queue-a, use_regex=true', async ({ page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues') + await page.goto('/queues#name=queue-a&use_regex=true') + await expect(apiQueuesRequest).toHaveQueryParams('name=queue-a&use_regex=true') + }) + + test('are sort=name, sort_reverse=false', async ({ page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues') + await page.goto('/queues#sort=name&sort_reverse=false') + await expect(apiQueuesRequest).toHaveQueryParams('sort=name&sort_reverse=false') + }) + + test('are page_size=10, page=3, name=qname, use_regex=true, sort=name, sort_reverse=true', async ({ page }) => { + const q = 'page_size=10&page=3&name=qname&use_regex=true&sort=name&sort_reverse=true' + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues') + await page.goto(`/queues#${q}`) + await expect(apiQueuesRequest).toHaveQueryParams(q) + }) + }) + + test.describe('sorting', _ => { + const queues = Array.from(Array(10), (_, i) => qHelpers.queue(`queue-${i}`)) + const queues_response = qHelpers.response(queues, { total_count: 100, page: 1, page_count: 10, page_size: 10 }) + + test('updates url when a table header is clicked', async ({ page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues', queues_response) + await page.goto('/queues') + await apiQueuesRequest + await page.locator('#table thead').getByText('Name').click() + await expect(page).toHaveURL(/sort=name/) + }) + + test('is reversed when click on the same header', async ({ page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues', queues_response) + await page.goto('/queues') + await apiQueuesRequest + const apiQueuesRequest2 = helpers.waitForPathRequest(page, '/api/queues', queues_response) + await page.locator('#table thead').getByText('Name').click() + await expect(page).toHaveURL(/sort=name/) + await apiQueuesRequest2 + const sort_reverse = (new URL(page.url())).searchParams.get('sort_reverse') == 'true' + const apiQueuesRequest3 = helpers.waitForPathRequest(page, '/api/queues', queues_response) + await page.locator('#table thead').getByText('Name').click() + await expect(page).toHaveURL(new RegExp(`sort_reverse=${!sort_reverse}`)) + await expect(apiQueuesRequest3).toHaveQueryParams(`sort_reverse=${!sort_reverse}`) + }) + }) + + test.describe('pagination', _ => { + const queues = Array.from(Array(10), (_, i) => qHelpers.queue(`queue-${i}`)) + const queues_response = qHelpers.response(queues, { total_count: 100, page: 1, page_count: 10, page_size: 10 }) + + test('is visible for page_count=10', async ({ page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues', queues_response) + await page.goto('/queues') + await apiQueuesRequest + await expect(page.locator('#table .pagination .page-item')).toContainText(['Previous', 'Next'], { timeout: 10 }) + }) + + test('updates url when Next is clicked', async ({page }) => { + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues', queues_response) + await page.goto('/queues') + await apiQueuesRequest + const apiQueuesRequest2 = helpers.waitForPathRequest(page, '/api/queues') + await page.locator('#table .pagination .page-item').getByText('Next').click() + await expect(page).toHaveURL(/page=2/) + await expect(apiQueuesRequest2).toHaveQueryParams('page=2') + }) + }) + + test.describe('search', _ => { + test('updates url when value is entered and Enter is hit', async ({ page })=> { + await page.goto('/queues') + const searchField = page.locator('.filter-table') + await searchField.fill('my filter') + const apiQueuesRequest = helpers.waitForPathRequest(page, '/api/queues') + await searchField.press('Enter') + await expect(page).toHaveURL(/name=my(\+|%20)filter/) + await expect(apiQueuesRequest).toHaveQueryParams('name=my filter&use_regex=true') + }) + }) +}) diff --git a/spec/frontend/queues_helpers.js b/spec/frontend/queues_helpers.js new file mode 100644 index 0000000000..7a631e7897 --- /dev/null +++ b/spec/frontend/queues_helpers.js @@ -0,0 +1,38 @@ +function queue(name, opts = {}) { + const defaults = { + name: name, + vhost: '/', + durable: true, + auto_delete: false, + exclusive: false, + arguments: [], + policy: '', + consumers: 0, + state: 'running', + ready: 0, + unacked: 0, + mesages: 0, + message_stats: { + publish_details: { rate: 0 }, + deliver_details: { rate: 0 }, + redeliver_details: { rate: 0 }, + ack_details: { rate: 0 }, + } + } + return Object.assign({}, defaults, opts) +} + +function response(queues = [], opts = {}) { + const defaults = { + page: 1, + page_size: queues.length, + page_count: queues.size > 0 ? 1 : 0, + filtered_count: queues.length, + total_count: queues.length, + item_count: queues.length, + items: queues + } + return Object.assign({}, defaults, opts) +} + +export { queue, response } diff --git a/spec/frontend/shovels.spec.js b/spec/frontend/shovels.spec.js new file mode 100644 index 0000000000..2ea46c883e --- /dev/null +++ b/spec/frontend/shovels.spec.js @@ -0,0 +1,12 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("shovels", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiShovelsRequest = helpers.waitForPathRequest(page, '/api/shovels', {}) + const apiParametersRequest = helpers.waitForPathRequest(page, '/api/parameters/shovel', {}) + await page.goto('/shovels') + await expect(apiShovelsRequest).toBeRequested() + await expect(apiParametersRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/users.spec.js b/spec/frontend/users.spec.js new file mode 100644 index 0000000000..d8137e8e67 --- /dev/null +++ b/spec/frontend/users.spec.js @@ -0,0 +1,10 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("users", _ => { + test('are loaded', async ({ page, baseURL }) => { + const apiUsersRequest = helpers.waitForPathRequest(page, '/api/users', {}) + await page.goto('/users') + await expect(apiUsersRequest).toBeRequested() + }) +}) diff --git a/spec/frontend/vhosts.spec.js b/spec/frontend/vhosts.spec.js new file mode 100644 index 0000000000..e1bf3338a1 --- /dev/null +++ b/spec/frontend/vhosts.spec.js @@ -0,0 +1,11 @@ +import * as helpers from './helpers.js' +import { test, expect } from './fixtures.js'; + +test.describe("vhosts", _ => { + test('are loaded', async ({ page, baseURL, vhosts }) => { + // vhosts are the vhosts returned in fixtures + const apiPermissionsRequests = vhosts.map(v => page.waitForRequest(`${baseURL}/api/vhosts/${v}/permissions`)) + await page.goto('/vhosts') + await expect(Promise.all(apiPermissionsRequests)).toBeRequested() + }) +})