feat(pkg): Add hda-cms-extension-e2e
This commit is contained in:
1
cog.toml
1
cog.toml
@@ -38,3 +38,4 @@ authors = [
|
|||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
hda-cms-extension = { path = "packages/hda-cms-extension" }
|
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
|
specifier: ^5.7.3
|
||||||
version: 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:
|
packages:
|
||||||
|
|
||||||
'@babel/code-frame@7.26.2':
|
'@babel/code-frame@7.26.2':
|
||||||
@@ -527,6 +542,11 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
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':
|
'@rollup/plugin-commonjs@25.0.8':
|
||||||
resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==}
|
resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@@ -1054,6 +1074,11 @@ packages:
|
|||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
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:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@@ -1403,6 +1428,16 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
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:
|
postcss-calc@8.2.4:
|
||||||
resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
|
resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2329,6 +2364,10 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@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)':
|
'@rollup/plugin-commonjs@25.0.8(rollup@3.29.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.1.4(rollup@3.29.5)
|
'@rollup/pluginutils': 5.1.4(rollup@3.29.5)
|
||||||
@@ -2919,6 +2958,9 @@ snapshots:
|
|||||||
|
|
||||||
fs.realpath@1.0.0: {}
|
fs.realpath@1.0.0: {}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -3217,6 +3259,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@vue/composition-api'
|
- '@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):
|
postcss-calc@8.2.4(postcss@8.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
|
|||||||
Reference in New Issue
Block a user