feat(pkg): Add hda-cms-extension-e2e
This commit is contained in:
1
cog.toml
1
cog.toml
@@ -38,3 +38,4 @@ authors = [
|
||||
|
||||
[packages]
|
||||
hda-cms-extension = { path = "packages/hda-cms-extension" }
|
||||
hda-cms-extension-e2e = { path = "packages/hda-cms-extension-e2e" }
|
||||
|
||||
2
packages/hda-cms-extension-e2e/docker/.dockerignore
Normal file
2
packages/hda-cms-extension-e2e/docker/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
directus-config.js
|
||||
docker-compose.yml
|
||||
22
packages/hda-cms-extension-e2e/docker/Dockerfile
Normal file
22
packages/hda-cms-extension-e2e/docker/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
ARG NODE_VERSION=22
|
||||
|
||||
####################################################################################################
|
||||
## Build Packages
|
||||
FROM node:${NODE_VERSION}-alpine AS builder
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN <<EOF
|
||||
if [ "$TARGETPLATFORM" = 'linux/arm64' ]; then
|
||||
apk --no-cache add python3 py3-pip build-base
|
||||
ln -sf /usr/bin/python3 /usr/bin/python
|
||||
fi
|
||||
EOF
|
||||
|
||||
WORKDIR /extensions
|
||||
RUN --mount=type=bind,source=directus-extensions.json,target=/extensions/package.json npm install
|
||||
RUN mkdir -p ./directus
|
||||
RUN grep -lR "directus:extension" ./node_modules/* | grep -E 'package.json$' | sed 's/\/package.json//;' | xargs -r -I % mv % ./directus
|
||||
|
||||
FROM tabshift4docker/directus:latest
|
||||
# Copy third party extensions
|
||||
COPY --from=builder /extensions/directus ./extensions
|
||||
24
packages/hda-cms-extension-e2e/docker/directus-config.d.ts
vendored
Normal file
24
packages/hda-cms-extension-e2e/docker/directus-config.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
const config: {
|
||||
PUBLIC_URL: string
|
||||
HOST: string
|
||||
PORT: number
|
||||
EXTENSIONS_AUTO_RELOAD: boolean
|
||||
DB_CLIENT: string
|
||||
DB_FILENAME: string
|
||||
SECRET: string
|
||||
NODE_ENV: string
|
||||
WEBSOCKETS_ENABLED: boolean
|
||||
CORS_ENABLED: boolean
|
||||
CORS_ORIGIN: boolean
|
||||
ADMIN_EMAIL: string
|
||||
ADMIN_PASSWORD: string
|
||||
ADMIN_TOKEN: string
|
||||
EMAIL_TRANSPORT: string
|
||||
EMAIL_SMTP_HOST: string
|
||||
EMAIL_SMTP_PORT: number
|
||||
EMAIL_SMTP_USER: string
|
||||
EMAIL_SMTP_PASSWORD: string
|
||||
EMAIL_SMTP_SECURE: boolean
|
||||
EMAIL_FROM: string
|
||||
}
|
||||
export default config
|
||||
28
packages/hda-cms-extension-e2e/docker/directus-config.js
Normal file
28
packages/hda-cms-extension-e2e/docker/directus-config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
PUBLIC_URL: 'http://localhost',
|
||||
HOST: '0.0.0.0',
|
||||
PORT: 8055,
|
||||
|
||||
EXTENSIONS_AUTO_RELOAD: true,
|
||||
|
||||
DB_CLIENT: 'sqlite3',
|
||||
DB_FILENAME: '/directus/database/database.sqlite',
|
||||
|
||||
SECRET: '016316D6-A8C1-498B-B42D-660B8D1C7BD8',
|
||||
NODE_ENV: 'development',
|
||||
WEBSOCKETS_ENABLED: true,
|
||||
CORS_ENABLED: true,
|
||||
CORS_ORIGIN: true,
|
||||
|
||||
ADMIN_EMAIL: 'admin@tabshift.dev',
|
||||
ADMIN_PASSWORD: 'K@QtaBG8ydC-GrTiHM',
|
||||
ADMIN_TOKEN: '4437384844958328Dj83jDjdie83h212',
|
||||
|
||||
EMAIL_TRANSPORT: 'smtp',
|
||||
EMAIL_SMTP_HOST: 'h125.hostmesh.de',
|
||||
EMAIL_SMTP_PORT: 587,
|
||||
EMAIL_SMTP_USER: 'notifier',
|
||||
EMAIL_SMTP_PASSWORD: 'Qhh.9dxze_cMkd6NdE-erDdsT',
|
||||
EMAIL_SMTP_SECURE: true,
|
||||
EMAIL_FROM: '"Tabshift Notifications" <notifications@tabshift.dev>'
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "directus-extensions",
|
||||
"dependencies": {
|
||||
"@directus-labs/card-select-interfaces": "^1.0.0",
|
||||
"@directus-labs/command-palette-module": "^1.0.1",
|
||||
"@directus-labs/experimental-m2a-interface": "^1.1.0",
|
||||
"@directus-labs/simple-list-interface": "^1.0.0",
|
||||
"@directus-labs/super-header-interface": "^1.1.0",
|
||||
"directus-extension-group-tabs-interface": "^2.1.0",
|
||||
"directus-extension-sync": "^3.0.2",
|
||||
"directus-extension-wpslug-interface": "^1.1.0"
|
||||
}
|
||||
}
|
||||
12
packages/hda-cms-extension-e2e/docker/docker-compose.yml
Normal file
12
packages/hda-cms-extension-e2e/docker/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
name: directus
|
||||
services:
|
||||
directus:
|
||||
image: directus-extension-hda
|
||||
build: .
|
||||
ports:
|
||||
- 8055:8055
|
||||
volumes:
|
||||
- ../../hda-cms-extension:/directus/extensions/directus-extension-hda
|
||||
- ./directus-config.js:/directus/extensions/directus-extension-hda/directus-config.js:r
|
||||
environment:
|
||||
- CONFIG_PATH=/directus/extensions/directus-extension-hda/directus-config.js
|
||||
17
packages/hda-cms-extension-e2e/package.json
Normal file
17
packages/hda-cms-extension-e2e/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@tabshift/hda-cms-extension-e2e",
|
||||
"description": "E2E tests for the CMS exension of Haus der Akademien",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"e2e-test": "playwright test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "latest",
|
||||
"@tabshift/typescript-config": "latest",
|
||||
"@types/node": "latest",
|
||||
"typescript": "latest"
|
||||
},
|
||||
"author": "T. R. Bernstein <bhdacms01-project@tabshift.dev>",
|
||||
"license": "EUPL-1.2"
|
||||
}
|
||||
85
packages/hda-cms-extension-e2e/playwright.config.ts
Normal file
85
packages/hda-cms-extension-e2e/playwright.config.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test'
|
||||
import type { WorkerConfigOptions } from './src/fixtures/config-options.js'
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import { fileURLToPath } from 'url'
|
||||
import * as path from 'path'
|
||||
import directus_config from './docker/directus-config.js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
const testDir = './src'
|
||||
const playwrightDir = path.join(__dirname, '.playwright')
|
||||
const outputDir = path.join(playwrightDir, 'Temporaries')
|
||||
const globalSessionFile = path.join(playwrightDir, 'session.js')
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig<{}, WorkerConfigOptions> = {
|
||||
testDir,
|
||||
outputDir,
|
||||
forbidOnly: !!process.env['CI'],
|
||||
retries: process.env['CI'] ? 2 : 0,
|
||||
workers: process.env['CI'] ? 1 : '100%',
|
||||
reporter: 'list',
|
||||
timeout: 60000,
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
baseURL: `${directus_config.PUBLIC_URL}:${directus_config.PORT}`,
|
||||
trace: 'on-first-retry',
|
||||
globalSessionFile
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: 'setup',
|
||||
testMatch: /.*\.setup\.ts/
|
||||
},
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome']
|
||||
},
|
||||
dependencies: ['setup']
|
||||
},
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
...devices['Desktop Firefox']
|
||||
},
|
||||
dependencies: ['setup']
|
||||
},
|
||||
{
|
||||
name: 'webkit',
|
||||
use: {
|
||||
...devices['Desktop Safari']
|
||||
},
|
||||
dependencies: ['setup']
|
||||
},
|
||||
/* Test against mobile viewports. */
|
||||
{
|
||||
name: 'Mobile Chrome',
|
||||
use: {
|
||||
...devices['Pixel 5']
|
||||
},
|
||||
dependencies: ['setup']
|
||||
},
|
||||
{
|
||||
name: 'Mobile Safari',
|
||||
use: {
|
||||
...devices['iPhone 12']
|
||||
},
|
||||
dependencies: ['setup']
|
||||
}
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: './setup.sh start-fg',
|
||||
url: `${directus_config.PUBLIC_URL}:${directus_config.PORT}`,
|
||||
reuseExistingServer: !process.env['CI'],
|
||||
gracefulShutdown: { signal: 'SIGTERM', timeout: 2000 }
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig(config)
|
||||
74
packages/hda-cms-extension-e2e/setup.sh
Executable file
74
packages/hda-cms-extension-e2e/setup.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env zsh
|
||||
TABSHIFT_DIRECTUS_PATH="${0:h}/../../apps/directus"
|
||||
TABSHIFT_DIRECTUS_NAME='tabshift4docker/directus'
|
||||
DOCKER_E2E_SETUP_PATH="${0:h}/docker"
|
||||
|
||||
start_docker_runtime() {
|
||||
colima status || colima start
|
||||
}
|
||||
|
||||
stop_docker_runtime() {
|
||||
colima status && colima stop
|
||||
}
|
||||
|
||||
build_tabshift_directus_image() {
|
||||
if docker image ls | grep ${TABSHIFT_DIRECTUS_NAME}; then
|
||||
return
|
||||
fi
|
||||
docker buildx build -t ${TABSHIFT_DIRECTUS_NAME}:latest --platform linux/arm64 $TABSHIFT_DIRECTUS_PATH
|
||||
}
|
||||
|
||||
start_docker_compose_service() {
|
||||
pushd -q ${DOCKER_E2E_SETUP_PATH}
|
||||
docker-compose up ${start_in_background:+'-d'}
|
||||
popd -q
|
||||
}
|
||||
|
||||
stop_docker_compose_services() {
|
||||
pushd -q ${DOCKER_E2E_SETUP_PATH}
|
||||
docker-compose stop
|
||||
popd -q
|
||||
}
|
||||
|
||||
start_docker_compose_service_and_runtime() {
|
||||
local start_in_background=${1:+'y'}
|
||||
start_docker_runtime
|
||||
build_tabshift_directus_image
|
||||
start_docker_compose_service
|
||||
}
|
||||
|
||||
start_docker_compose_service_in_background() {
|
||||
start_docker_compose_service_and_runtime start_in_background
|
||||
}
|
||||
|
||||
start_docker_compose_service_in_foreground() {
|
||||
start_docker_compose_service_and_runtime
|
||||
}
|
||||
|
||||
stop_docker_compose_services_and_runtime() {
|
||||
stop_docker_compose_services
|
||||
stop_docker_runtime
|
||||
}
|
||||
|
||||
trap 'docker container prune -f' INT TERM EXIT
|
||||
|
||||
main() {
|
||||
local CMD=$1
|
||||
|
||||
case $CMD in
|
||||
start)
|
||||
start_docker_compose_service_in_background
|
||||
;;
|
||||
start-fg)
|
||||
start_docker_compose_service_in_foreground
|
||||
;;
|
||||
stop-all)
|
||||
stop_docker_compose_services_and_runtime
|
||||
;;
|
||||
*)
|
||||
stop_docker_compose_services
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main $*
|
||||
@@ -0,0 +1,8 @@
|
||||
import { expect, test } from '../fixtures/worker-specific-admin-user.js'
|
||||
import { navigateTo } from '../fixtures/navigation.js'
|
||||
|
||||
test('Antrago FTP Import operation extension is listed on extension page', async ({ workerAdminPage }) => {
|
||||
const page = workerAdminPage
|
||||
await navigateTo(page, '/settings/extensions')
|
||||
await expect(page.getByRole('main')).toContainText('antrago-ftp-import')
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect, test } from '../fixtures/page-with-flow.js'
|
||||
import { navigateTo } from '../fixtures/navigation.js'
|
||||
import { getFlowNameForWorker } from '../fixtures/page-with-flow.js'
|
||||
import enLang from '../../../hda-cms-extension/src/antrago-ftp-import/i18n/en-US.json' with { type: 'json' }
|
||||
import deLang from '../../../hda-cms-extension/src/antrago-ftp-import/i18n/de-DE.json' with { type: 'json' }
|
||||
import { setLanguageToGerman, setLanguageToEnglish } from '../fixtures/language-setter.js'
|
||||
import { getAccountForWorker } from '../fixtures/worker-specific-admin-user.js'
|
||||
|
||||
async function openOperationListOfFlow(page: Page, flowName: string) {
|
||||
await navigateTo(page, '/settings/flows')
|
||||
await page.getByText(flowName).click()
|
||||
await page.getByRole('main').locator('i').nth(4).click()
|
||||
}
|
||||
|
||||
async function verifyOperationIsListed(page: Page, operationName: string) {
|
||||
await page.reload()
|
||||
await expect(page.getByRole('article')).toContainText(operationName)
|
||||
}
|
||||
|
||||
test('FTP import operation is listed in flow items list in english & german', async ({
|
||||
globalSessionPage,
|
||||
pageWithFlow
|
||||
}, testInfo) => {
|
||||
const page = pageWithFlow
|
||||
const account = getAccountForWorker(testInfo.workerIndex)
|
||||
const flowName = getFlowNameForWorker(testInfo.workerIndex)
|
||||
|
||||
await openOperationListOfFlow(page, flowName)
|
||||
await verifyOperationIsListed(page, enLang.name)
|
||||
await setLanguageToGerman(globalSessionPage, account.fullname)
|
||||
await verifyOperationIsListed(page, deLang.name)
|
||||
await setLanguageToEnglish(globalSessionPage, account.fullname)
|
||||
})
|
||||
|
||||
test('FTP import operation has "FTP Server" config field', async ({ pageWithFlow }, testInfo) => {
|
||||
const page = pageWithFlow
|
||||
const flowName = getFlowNameForWorker(testInfo.workerIndex)
|
||||
|
||||
await openOperationListOfFlow(page, flowName)
|
||||
await page.getByText(enLang.name).click()
|
||||
await expect(page.getByRole('article')).toContainText(enLang.options.ftpserver.name)
|
||||
await expect(page.getByRole('article')).toContainText(enLang.options.ftpuser.name)
|
||||
await expect(page.getByRole('article')).toContainText(enLang.options.ftppassword.name)
|
||||
})
|
||||
|
||||
test('FTP import operation shows config details in box', async ({ pageWithFlow }, testInfo) => {
|
||||
const page = pageWithFlow
|
||||
const flowName = getFlowNameForWorker(testInfo.workerIndex)
|
||||
|
||||
await openOperationListOfFlow(page, flowName)
|
||||
await page.getByText(enLang.name).click()
|
||||
await page.locator('.interface > .v-input > .input').first().click()
|
||||
await page.getByRole('textbox').nth(2).fill('Some Server')
|
||||
await page.getByRole('textbox').nth(2).press('Tab')
|
||||
await page.getByRole('textbox').nth(3).fill('Some Username')
|
||||
await page.getByRole('textbox').nth(3).press('Tab')
|
||||
await page.locator('input[type="password"]').fill('Some Password')
|
||||
await page.locator('#dialog-outlet').getByRole('button', { name: 'check' }).click()
|
||||
await expect(page.getByRole('main').first()).toContainText('Some Username@Some Server')
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import { expect, test } from './fixtures/worker-specific-admin-user.js'
|
||||
import packageInfo from '../../hda-cms-extension/package.json' with { type: 'json' }
|
||||
import { navigateTo } from './fixtures/navigation.js'
|
||||
|
||||
test('bundle extension is listed on extension page', async ({ workerAdminPage }) => {
|
||||
const page = workerAdminPage
|
||||
await navigateTo(page, '/settings/extensions')
|
||||
await expect(page.getByRole('main')).toContainText(packageInfo.name)
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import { test as base } from '@playwright/test'
|
||||
|
||||
export interface WorkerConfigOptions {
|
||||
globalSessionFile: string
|
||||
}
|
||||
|
||||
export * from '@playwright/test'
|
||||
export const test = base.extend<{}, WorkerConfigOptions>({
|
||||
globalSessionFile: ['.playwright/session.js', { scope: 'worker', option: true }]
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { navigateTo } from './navigation.js'
|
||||
|
||||
const searchOptions = {
|
||||
'de-DE': {
|
||||
searchStr: 'ger',
|
||||
select: 'German (Germany)'
|
||||
},
|
||||
'en-US': {
|
||||
searchStr: 'engl',
|
||||
select: 'English (United States)'
|
||||
}
|
||||
}
|
||||
|
||||
async function setLanguage(page: Page, user: string, language: keyof typeof searchOptions) {
|
||||
const options = searchOptions[language]
|
||||
await navigateTo(page, '/users')
|
||||
await page.getByText(user).click()
|
||||
await page.locator('.v-menu-activator > .v-input > .input').first().click()
|
||||
await page.getByRole('textbox', { name: 'Search' }).fill(options.searchStr)
|
||||
await page.getByText(options.select).click()
|
||||
await page.getByRole('button', { name: 'check', exact: true }).click()
|
||||
}
|
||||
|
||||
export async function setLanguageToGerman(page: Page, user: string) {
|
||||
await setLanguage(page, user, 'de-DE')
|
||||
}
|
||||
|
||||
export async function setLanguageToEnglish(page: Page, user: string) {
|
||||
await setLanguage(page, user, 'en-US')
|
||||
}
|
||||
59
packages/hda-cms-extension-e2e/src/fixtures/navigation.ts
Normal file
59
packages/hda-cms-extension-e2e/src/fixtures/navigation.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
type ButtonName = string | [string, boolean]
|
||||
|
||||
interface NavigationOption {
|
||||
navigationButtonNames: ButtonName[]
|
||||
}
|
||||
|
||||
const navigationDestinations = {
|
||||
'/settings/flows': {
|
||||
navigationButtonNames: [['settings', true], 'bolt Flows']
|
||||
} as NavigationOption,
|
||||
'/users': {
|
||||
navigationButtonNames: ['people_alt']
|
||||
} as NavigationOption,
|
||||
'/settings/project': {
|
||||
navigationButtonNames: [['settings', true], 'tune Settings']
|
||||
} as NavigationOption,
|
||||
'/settings/extensions': {
|
||||
navigationButtonNames: [['settings', true], 'category Extensions']
|
||||
} as NavigationOption
|
||||
}
|
||||
|
||||
let isMobile: boolean | undefined = undefined
|
||||
|
||||
async function openMobileMenu(page: Page) {
|
||||
const hamburgerButton = page.getByRole('button', { name: 'menu' })
|
||||
|
||||
if (isMobile != false) {
|
||||
try {
|
||||
await hamburgerButton.click({ timeout: 1000 })
|
||||
if (isMobile == undefined) isMobile = true
|
||||
} catch {
|
||||
if (isMobile == undefined) isMobile = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function navigateUsingMenu(page: Page, buttonNames: ButtonName[]) {
|
||||
for (let buttonName of buttonNames) {
|
||||
let isExact = false
|
||||
if (typeof buttonName != 'string') {
|
||||
isExact = buttonName[1]
|
||||
buttonName = buttonName[0]
|
||||
}
|
||||
openMobileMenu(page)
|
||||
await page.getByRole('link', { name: buttonName, exact: isExact }).first().click({ timeout: 1000 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function navigateTo(page: Page, link: keyof typeof navigationDestinations) {
|
||||
const navigationOption = navigationDestinations[link]
|
||||
try {
|
||||
await navigateUsingMenu(page, navigationOption.navigationButtonNames)
|
||||
} catch {
|
||||
await page.goto(`/admin${link}`)
|
||||
}
|
||||
await page.reload()
|
||||
}
|
||||
67
packages/hda-cms-extension-e2e/src/fixtures/page-actions.ts
Normal file
67
packages/hda-cms-extension-e2e/src/fixtures/page-actions.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
import directusConfig from '../../docker/directus-config.js'
|
||||
|
||||
export type Account = {
|
||||
username: string
|
||||
fullname: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export async function loginAsAccount(page: Page, account: Account) {
|
||||
await page.goto('/admin/login')
|
||||
try {
|
||||
await page.getByRole('button', { name: 'Continue' }).click({ timeout: 300 })
|
||||
} catch {
|
||||
await page.getByRole('textbox', { name: 'Email' }).click()
|
||||
await page.getByRole('textbox', { name: 'Email' }).fill(account.username)
|
||||
await page.getByRole('textbox', { name: 'Password' }).click()
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill(account.password)
|
||||
await page.getByRole('button', { name: 'Sign In' }).click()
|
||||
}
|
||||
await page.waitForURL(/.*\/admin\/(?!login|logout)/g)
|
||||
}
|
||||
|
||||
export async function createAdminAccount(page: Page, account: Account) {
|
||||
await page.goto('/admin/users')
|
||||
try {
|
||||
await expect(page.getByRole('main')).not.toContainText(account.username)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await page.getByRole('link', { name: 'add' }).click()
|
||||
await page.locator('input').first().click()
|
||||
await page.locator('input').first().fill(account.fullname)
|
||||
|
||||
await page.locator('input').nth(2).click()
|
||||
await page.locator('input').nth(2).fill(account.username)
|
||||
|
||||
await page.locator('input[type="password"]').first().click()
|
||||
await page.locator('input[type="password"]').first().fill(account.password)
|
||||
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Role$/ })
|
||||
.first()
|
||||
.click()
|
||||
await expect(page.getByText('Directus RolesSelect')).toBeVisible()
|
||||
await page.getByRole('checkbox', { name: 'radio_button_unchecked' }).click()
|
||||
await page.locator('#dialog-outlet').getByRole('button', { name: 'check' }).click()
|
||||
await page.getByRole('button', { name: 'check', exact: true }).first().click()
|
||||
}
|
||||
|
||||
export async function logout(page: Page) {
|
||||
await page.goto('/admin/users')
|
||||
await page.getByRole('link', { name: 'account_circle' }).hover()
|
||||
await page.getByRole('navigation', { name: 'Module Navigation' }).getByRole('button').nth(1).hover()
|
||||
await page.getByRole('navigation', { name: 'Module Navigation' }).getByRole('button').nth(1).click()
|
||||
await page.getByRole('link', { name: 'Sign Out' }).click()
|
||||
await page.waitForURL(/.*\/admin\/login/g)
|
||||
}
|
||||
|
||||
export async function loginAsMainAdmin(page: Page) {
|
||||
await loginAsAccount(page, {
|
||||
username: directusConfig.ADMIN_EMAIL,
|
||||
password: directusConfig.ADMIN_PASSWORD
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { test as base } from './worker-specific-admin-user.js'
|
||||
import { navigateTo } from './navigation.js'
|
||||
|
||||
async function createFlow(page: Page, name: string) {
|
||||
await navigateTo(page, '/settings/flows')
|
||||
await page.getByRole('button', { name: 'add' }).first().click()
|
||||
await page.getByRole('textbox', { name: 'Name' }).fill(name)
|
||||
await page.getByRole('button', { name: 'arrow_forward' }).click()
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^ManualTriggers on manual run within selected collection\(s\)$/ })
|
||||
.first()
|
||||
.click()
|
||||
await page.getByRole('button', { name: 'check' }).click()
|
||||
await page.getByRole('button', { name: 'check', exact: true }).first().click()
|
||||
}
|
||||
|
||||
export function getFlowNameForWorker(workerIndex: number) {
|
||||
return `Flow #${workerIndex}`
|
||||
}
|
||||
|
||||
export * from '@playwright/test'
|
||||
export const test = base.extend<{}, { flowName: string; pageWithFlow: Page }>({
|
||||
pageWithFlow: [
|
||||
async ({ workerAdminPage }, use, workerInfo) => {
|
||||
const page = workerAdminPage
|
||||
const flowName = getFlowNameForWorker(workerInfo.workerIndex)
|
||||
await createFlow(page, flowName)
|
||||
await use(page)
|
||||
},
|
||||
{ scope: 'worker', title: 'Worker Flow Page' }
|
||||
]
|
||||
})
|
||||
56
packages/hda-cms-extension-e2e/src/fixtures/session-page.ts
Normal file
56
packages/hda-cms-extension-e2e/src/fixtures/session-page.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { Browser, Page, WorkerInfo } from '@playwright/test'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { test as base } from './config-options.js'
|
||||
|
||||
function getSessionFilenameForWorker(worker: number): string {
|
||||
const filename = path.resolve(test.info().project.outputDir, `session-data_worker-${worker}.js`)
|
||||
return filename
|
||||
}
|
||||
|
||||
async function getSessionPage(browser: Browser, sessionFile: string) {
|
||||
if (fs.existsSync(sessionFile)) {
|
||||
return await browser.newPage({ storageState: sessionFile })
|
||||
} else {
|
||||
return await browser.newPage()
|
||||
}
|
||||
}
|
||||
|
||||
async function storeSession(page: Page, sessionFile: string) {
|
||||
const context = page.context()
|
||||
await context.storageState({ path: sessionFile })
|
||||
}
|
||||
|
||||
async function getPageFixture(browser: Browser, use: (r: Page) => Promise<void>, sessionFile: string) {
|
||||
const page = await getSessionPage(browser, sessionFile)
|
||||
await use(page)
|
||||
await storeSession(page, sessionFile)
|
||||
await page.close()
|
||||
}
|
||||
|
||||
async function getWorkerSessionPage(
|
||||
{ browser }: { browser: Browser },
|
||||
use: (r: Page) => Promise<void>,
|
||||
workerInfo: WorkerInfo
|
||||
) {
|
||||
const workerIndex = workerInfo.workerIndex
|
||||
const sessionFile = getSessionFilenameForWorker(workerIndex)
|
||||
await getPageFixture(browser, use, sessionFile)
|
||||
}
|
||||
|
||||
async function getGlobalSessionPage(
|
||||
{ browser, globalSessionFile }: { browser: Browser; globalSessionFile: string },
|
||||
use: (r: Page) => Promise<void>
|
||||
) {
|
||||
await getPageFixture(browser, use, globalSessionFile)
|
||||
}
|
||||
|
||||
export interface SessionPageOptions {
|
||||
workerSessionPage: Page
|
||||
globalSessionPage: Page
|
||||
}
|
||||
export * from '@playwright/test'
|
||||
export const test = base.extend<{}, SessionPageOptions>({
|
||||
workerSessionPage: [getWorkerSessionPage, { scope: 'worker', title: 'Worker Session Page' }],
|
||||
globalSessionPage: [getGlobalSessionPage, { scope: 'worker', title: 'Global Session Page' }]
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { Account } from './page-actions.js'
|
||||
import { test as base } from './session-page.js'
|
||||
import { createAdminAccount, loginAsAccount } from './page-actions.js'
|
||||
|
||||
export function getAccountForWorker(worker: number): Account {
|
||||
return {
|
||||
username: `user${worker}@example.com`,
|
||||
fullname: `Worker #${worker}`,
|
||||
password: 'sameoldpasswordwithextrasteps'
|
||||
}
|
||||
}
|
||||
|
||||
export * from '@playwright/test'
|
||||
export const test = base.extend<{}, { workerAdminPage: Page }>({
|
||||
workerAdminPage: [
|
||||
async ({ globalSessionPage, workerSessionPage }, use, workerInfo) => {
|
||||
const workerAdminAccount = getAccountForWorker(workerInfo.workerIndex)
|
||||
await createAdminAccount(globalSessionPage, workerAdminAccount)
|
||||
await loginAsAccount(workerSessionPage, workerAdminAccount)
|
||||
await use(workerSessionPage)
|
||||
},
|
||||
{ scope: 'worker', title: 'Worker Admin User' }
|
||||
]
|
||||
})
|
||||
6
packages/hda-cms-extension-e2e/src/login.setup.ts
Normal file
6
packages/hda-cms-extension-e2e/src/login.setup.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { test as setup } from './fixtures/session-page.js'
|
||||
import { loginAsMainAdmin } from './fixtures/page-actions.js'
|
||||
|
||||
setup('Authenticate as main admin', async ({ globalSessionPage }) => {
|
||||
await loginAsMainAdmin(globalSessionPage)
|
||||
})
|
||||
3
packages/hda-cms-extension-e2e/tsconfig.json
Normal file
3
packages/hda-cms-extension-e2e/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@tabshift/typescript-config/base.json"
|
||||
}
|
||||
50
pnpm-lock.yaml
generated
50
pnpm-lock.yaml
generated
@@ -30,6 +30,21 @@ importers:
|
||||
specifier: ^5.7.3
|
||||
version: 5.7.3
|
||||
|
||||
packages/hda-cms-extension-e2e:
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: latest
|
||||
version: 1.50.1
|
||||
'@tabshift/typescript-config':
|
||||
specifier: latest
|
||||
version: 1.0.0
|
||||
'@types/node':
|
||||
specifier: latest
|
||||
version: 22.13.5
|
||||
typescript:
|
||||
specifier: latest
|
||||
version: 5.7.3
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/code-frame@7.26.2':
|
||||
@@ -527,6 +542,11 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@playwright/test@1.50.1':
|
||||
resolution: {integrity: sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rollup/plugin-commonjs@25.0.8':
|
||||
resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -1054,6 +1074,11 @@ packages:
|
||||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1403,6 +1428,16 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
playwright-core@1.50.1:
|
||||
resolution: {integrity: sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.50.1:
|
||||
resolution: {integrity: sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
postcss-calc@8.2.4:
|
||||
resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
|
||||
peerDependencies:
|
||||
@@ -2329,6 +2364,10 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
'@playwright/test@1.50.1':
|
||||
dependencies:
|
||||
playwright: 1.50.1
|
||||
|
||||
'@rollup/plugin-commonjs@25.0.8(rollup@3.29.5)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@3.29.5)
|
||||
@@ -2919,6 +2958,9 @@ snapshots:
|
||||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -3217,6 +3259,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
playwright-core@1.50.1: {}
|
||||
|
||||
playwright@1.50.1:
|
||||
dependencies:
|
||||
playwright-core: 1.50.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
postcss-calc@8.2.4(postcss@8.5.3):
|
||||
dependencies:
|
||||
postcss: 8.5.3
|
||||
|
||||
Reference in New Issue
Block a user