Skip to content

Commit a8f955e

Browse files
ochorochobnf
authored andcommitted
[TASK] Migrate PageCreationWithDragAndDropCest.php to Playwright
The codeception acceptance test PageCreationWithDragAndDropCest.php had to be skipped since the page tree was migrated to use native browser drag-drop functionality in #102125, because codeception/webdriver can not emulate native drag-drop events. The test is now migrated to an end-to-end test based on Playwright. Playwright is already used for accessibility tests and can now act as a modern replacement for codeception, as it allows to send real mouse and keyboard instructions instead of faking DOM events. Resolves: #105081 Releases: main, 13.4 Change-Id: I282a427395a4d625914f54178e06494357b8e39c Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/86256 Reviewed-by: Benjamin Franzke <ben@bnf.dev> Tested-by: Benjamin Kott <benjamin.kott@outlook.com> Tested-by: Benjamin Franzke <ben@bnf.dev> Reviewed-by: Benjamin Kott <benjamin.kott@outlook.com> Tested-by: core-ci <typo3@b13.com>
1 parent da0df56 commit a8f955e

File tree

10 files changed

+210
-324
lines changed

10 files changed

+210
-324
lines changed

Build/Scripts/runTests.sh

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,73 @@ getPhpImageVersion() {
175175
esac
176176
}
177177

178+
# @todo: Add support for all available database engines (see -d option)
179+
# @todo: Add support for classic mode
180+
runPlaywright() {
181+
PREPAREPARAMS="-e TYPO3_DB_DRIVER=sqlite"
182+
TESTPARAMS="-e typo3DatabaseDriver=pdo_sqlite"
183+
184+
if [ "${PLAYWRIGHT_USE_EXISTING_INSTANCE}x" = "x" ]; then
185+
rm -rf "${CORE_ROOT}/typo3temp/var/tests/playwright-composer" "${CORE_ROOT}/typo3temp/var/tests/playwright-reports" "${CORE_ROOT}/typo3temp/var/tests/playwright-results"
186+
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name playwright-prepare ${XDEBUG_MODE} -e COMPOSER_CACHE_DIR=${CORE_ROOT}/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${PREPAREPARAMS} ${IMAGE_PHP} "${CORE_ROOT}/Build/Scripts/setupAcceptanceComposer.sh" "typo3temp/var/tests/playwright-composer" sqlite
187+
if [[ $? -gt 0 ]]; then
188+
kill -SIGINT -$$
189+
fi
190+
fi
191+
192+
[[ -e "${CORE_ROOT}/Build/node_modules/.bin/playwright" ]] || ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name playwright-${SUFFIX}-npm-ci \
193+
-e HOME=${CORE_ROOT}/.cache \
194+
${IMAGE_NODEJS_CHROME} \
195+
npm --prefix=Build ci
196+
if [[ $? -gt 0 ]]; then
197+
kill -SIGINT -$$
198+
fi
199+
200+
APACHE_OPTIONS="-e APACHE_RUN_USER=#${HOST_UID} -e APACHE_RUN_SERVERNAME=web -e APACHE_RUN_GROUP=#${HOST_PID} -e APACHE_RUN_DOCROOT=${CORE_ROOT}/typo3temp/var/tests/playwright-composer/public -e PHPFPM_HOST=phpfpm -e PHPFPM_PORT=9000"
201+
if [[ ${PLAYWRIGHT_PREPARE_ONLY} -eq 1 ]]; then
202+
APACHE_OPTIONS="${APACHE_OPTIONS} -p 127.0.0.1::80"
203+
fi
204+
205+
if [ ${CONTAINER_BIN} = "docker" ]; then
206+
${CONTAINER_BIN} run --rm -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -e PHPFPM_USER=${HOST_UID} -e PHPFPM_GROUP=${HOST_PID} -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm ${PHP_FPM_OPTIONS} >/dev/null
207+
${CONTAINER_BIN} run --rm -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web --add-host "${CONTAINER_HOST}:host-gateway" -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null
208+
else
209+
${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm ${USERSET} -e PHPFPM_USER=0 -e PHPFPM_GROUP=0 -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm -R ${PHP_FPM_OPTIONS} >/dev/null
210+
${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null
211+
fi
212+
213+
waitFor web 80
214+
215+
COMMAND="npm --prefix=${CORE_ROOT}/Build run playwright:run ${PLAYWRIGHT_PROJECT}"
216+
COMMAND_UI="npm --prefix=${CORE_ROOT}/Build run playwright:open ${PLAYWRIGHT_PROJECT}"
217+
if [[ ${PLAYWRIGHT_PREPARE_ONLY} -eq 0 ]]; then
218+
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name accessibility-${SUFFIX} -e CHROME_SANDBOX=false -e CI=1 ${IMAGE_PLAYWRIGHT} ${COMMAND}
219+
SUITE_EXIT_CODE=$?
220+
else
221+
PLAYWRIGHT_BASE_URL="http://$(${CONTAINER_BIN} port ac-web-${SUFFIX} 80/tcp)/"
222+
echo
223+
echo -en "\033[32m✓\033[0m "
224+
echo "Environment prepared. You can now press Enter to run all tests or run playwright locally with one of the following commands."
225+
echo
226+
echo " Run with local playwright (headless):"
227+
echo -n " "
228+
echo "PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL}typo3 ${COMMAND}"
229+
echo
230+
echo " Open local playwright UI:"
231+
echo -n " "
232+
echo "PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL}typo3 ${COMMAND_UI}"
233+
echo
234+
echo -e "(Press \033[31mControl-C\033[0m to quit, \033[32mEnter\033[0m to run tests in container)"
235+
# maybe use https://stackoverflow.com/a/58508884/4223467
236+
while read -r _; do
237+
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name accessibility-${SUFFIX} -e CHROME_SANDBOX=false -e CI=1 ${IMAGE_PLAYWRIGHT} ${COMMAND}
238+
SUITE_EXIT_CODE=$?
239+
echo
240+
echo -e "(Press \033[31mControl-C\033[0m to quit, \033[32mEnter\033[0m to re-run tests in container)"
241+
done </dev/tty
242+
fi
243+
}
244+
178245
loadHelp() {
179246
# Load help text into $HELP
180247
read -r -d '' HELP <<EOF
@@ -228,7 +295,8 @@ Options:
228295
- lintTypescript: TS linting
229296
- lintYaml: YAML Linting (excluding Services.yaml)
230297
- npm: "npm" command dispatcher, to execute various npm commands directly
231-
- accessibility: accessibility tests
298+
- accessibility: accessibility tests (use accessibility-prepare for manual execution)
299+
- e2e: end to end tests (use e2e-prepare for manual execution)
232300
- phpstan: phpstan tests
233301
- phpstanGenerateBaseline: regenerate phpstan baseline, handy after phpstan updates
234302
- unit (default): PHP unit tests
@@ -796,64 +864,25 @@ case ${TEST_SUITE} in
796864
;;
797865
esac
798866
;;
799-
accessibility*)
800-
[[ "$TEST_SUITE" = 'accessibility-prepare' ]] && ACCESSIBILITY_PREPARE=1 || ACCESSIBILITY_PREPARE=0
801-
PREPAREPARAMS="-e TYPO3_DB_DRIVER=sqlite"
802-
TESTPARAMS="-e typo3DatabaseDriver=pdo_sqlite"
803-
804-
if [ "${ACCESSIBILITY_USE_EXISTING_INSTANCE}x" = "x" ]; then
805-
rm -rf "${CORE_ROOT}/typo3temp/var/tests/playwright-composer" "${CORE_ROOT}/typo3temp/var/tests/playwright-reports" "${CORE_ROOT}/typo3temp/var/tests/playwright-results"
806-
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name accessibility-prepare ${XDEBUG_MODE} -e COMPOSER_CACHE_DIR=${CORE_ROOT}/.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${PREPAREPARAMS} ${IMAGE_PHP} "${CORE_ROOT}/Build/Scripts/setupAcceptanceComposer.sh" "typo3temp/var/tests/playwright-composer" sqlite
807-
if [[ $? -gt 0 ]]; then
808-
kill -SIGINT -$$
809-
fi
810-
fi
811-
812-
[[ -e "${CORE_ROOT}/Build/node_modules/.bin/playwright" ]] || ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name playwright-${SUFFIX}-npm-ci \
813-
-e HOME=${CORE_ROOT}/.cache \
814-
${IMAGE_NODEJS_CHROME} \
815-
npm --prefix=Build ci
816-
if [[ $? -gt 0 ]]; then
817-
kill -SIGINT -$$
818-
fi
819-
820-
APACHE_OPTIONS="-e APACHE_RUN_USER=#${HOST_UID} -e APACHE_RUN_SERVERNAME=web -e APACHE_RUN_GROUP=#${HOST_PID} -e APACHE_RUN_DOCROOT=${CORE_ROOT}/typo3temp/var/tests/playwright-composer/public -e PHPFPM_HOST=phpfpm -e PHPFPM_PORT=9000"
821-
if [[ ${ACCESSIBILITY_PREPARE} -eq 1 ]]; then
822-
APACHE_OPTIONS="${APACHE_OPTIONS} -p 127.0.0.1::80"
823-
fi
824-
825-
if [ ${CONTAINER_BIN} = "docker" ]; then
826-
${CONTAINER_BIN} run --rm -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -e PHPFPM_USER=${HOST_UID} -e PHPFPM_GROUP=${HOST_PID} -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm ${PHP_FPM_OPTIONS} >/dev/null
827-
${CONTAINER_BIN} run --rm -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web --add-host "${CONTAINER_HOST}:host-gateway" -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null
828-
else
829-
${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-phpfpm-${SUFFIX} --network ${NETWORK} --network-alias phpfpm ${USERSET} -e PHPFPM_USER=0 -e PHPFPM_GROUP=0 -v ${CORE_ROOT}:${CORE_ROOT} ${IMAGE_PHP} php-fpm -R ${PHP_FPM_OPTIONS} >/dev/null
830-
${CONTAINER_BIN} run --rm ${CI_PARAMS} -d --name ac-web-${SUFFIX} --network ${NETWORK} --network-alias web -v ${CORE_ROOT}:${CORE_ROOT} ${APACHE_OPTIONS} ${IMAGE_APACHE} >/dev/null
831-
fi
832-
833-
waitFor web 80
834-
835-
COMMAND="npm --prefix=${CORE_ROOT}/Build run playwright:run"
836-
if [[ ${ACCESSIBILITY_PREPARE} -eq 0 ]]; then
837-
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name accessibility-${SUFFIX} -e CHROME_SANDBOX=false -e CI=1 ${IMAGE_PLAYWRIGHT} ${COMMAND}
838-
SUITE_EXIT_CODE=$?
839-
else
840-
ACCESSIBILITY_BASE_URL="http://$(${CONTAINER_BIN} port ac-web-${SUFFIX} 80/tcp)/"
841-
echo
842-
echo -en "\033[32m✓\033[0m "
843-
echo "Environment prepared. You can now manually run the following command or press Enter to run all tests."
844-
echo
845-
echo -n " "
846-
echo "ACCESSIBILITY_BASE_URL=${ACCESSIBILITY_BASE_URL}typo3 ${COMMAND}"
847-
echo
848-
echo -e "(Press \033[31mControl-C\033[0m to quit, \033[32mEnter\033[0m to run tests)"
849-
# maybe use https://stackoverflow.com/a/58508884/4223467
850-
while read -r _; do
851-
${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name accessibility-${SUFFIX} -e CHROME_SANDBOX=false -e CI=1 ${IMAGE_PLAYWRIGHT} ${COMMAND}
852-
SUITE_EXIT_CODE=$?
853-
echo
854-
echo -e "(Press \033[31mControl-C\033[0m to quit, \033[32mEnter\033[0m to re-run tests)"
855-
done </dev/tty
856-
fi
867+
e2e)
868+
PLAYWRIGHT_PROJECT="--project e2e"
869+
PLAYWRIGHT_PREPARE_ONLY=0
870+
runPlaywright
871+
;;
872+
e2e-prepare)
873+
PLAYWRIGHT_PROJECT="--project e2e"
874+
PLAYWRIGHT_PREPARE_ONLY=1
875+
runPlaywright
876+
;;
877+
accessibility)
878+
PLAYWRIGHT_PROJECT="--project accessibility"
879+
PLAYWRIGHT_PREPARE_ONLY=0
880+
runPlaywright
881+
;;
882+
accessibility-prepare)
883+
PLAYWRIGHT_PROJECT="--project accessibility"
884+
PLAYWRIGHT_PREPARE_ONLY=1
885+
runPlaywright
857886
;;
858887
buildCss)
859888
COMMAND="cd Build; npm ci || exit 1; node_modules/grunt/bin/grunt css"

Build/gitlab-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ include:
4545
# turns this into a branch 'change-patchset' which executes the pipeline
4646
- local: 'Build/gitlab-ci/pre-merge/acceptance-install.yml'
4747
- local: 'Build/gitlab-ci/pre-merge/acceptance-application.yml'
48+
- local: 'Build/gitlab-ci/pre-merge/e2e.yml'
4849
- local: 'Build/gitlab-ci/pre-merge/accessibility.yml'
4950
- local: 'Build/gitlab-ci/pre-merge/integrity.yml'
5051
- local: 'Build/gitlab-ci/pre-merge/functional.yml'
@@ -55,5 +56,6 @@ include:
5556
- local: 'Build/gitlab-ci/nightly/acceptance-install.yml'
5657
- local: 'Build/gitlab-ci/nightly/acceptance-application.yml'
5758
- local: 'Build/gitlab-ci/nightly/acceptance-application-composer.yml'
59+
- local: 'Build/gitlab-ci/nightly/e2e.yml'
5860
- local: 'Build/gitlab-ci/nightly/accessibility.yml'
5961
- local: 'Build/gitlab-ci/nightly/functional.yml'

Build/gitlab-ci/nightly/e2e.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
e2e playwright composer sqlite 8.4:
2+
stage: acceptance
3+
tags:
4+
- metal2
5+
needs: []
6+
only:
7+
- schedules
8+
artifacts:
9+
when: on_failure
10+
paths:
11+
- typo3temp/var/tests/playwright-composer/var/log
12+
- typo3temp/var/tests/playwright-reports
13+
script:
14+
- Build/Scripts/runTests.sh -s e2e -p 8.4 -d sqlite

Build/gitlab-ci/pre-merge/e2e.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
e2e playwright composer sqlite 8.2 pre-merge:
2+
stage: main
3+
tags:
4+
- metal2
5+
except:
6+
refs:
7+
- schedules
8+
- main
9+
artifacts:
10+
when: on_failure
11+
paths:
12+
- typo3temp/var/tests/playwright-composer/var/log
13+
- typo3temp/var/tests/playwright-reports
14+
script:
15+
- Build/Scripts/runTests.sh -s e2e -p 8.2 -d sqlite

Build/playwright.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ export default defineConfig({
4141
storageState: path.join(__dirname, '.auth/login.json'),
4242
},
4343
},
44+
{
45+
name: 'e2e',
46+
testMatch: 'e2e/**/*.spec.ts',
47+
dependencies: ['login'],
48+
use: {
49+
storageState: path.join(__dirname, '.auth/login.json'),
50+
},
51+
},
4452
],
4553
outputDir: '../typo3temp/var/tests/playwright-results'
4654
});

Build/tests/playwright/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export default {
2-
baseUrl: process.env.ACCESSIBILITY_BASE_URL || 'http://web:80/typo3',
2+
baseUrl: process.env.PLAYWRIGHT_BASE_URL || 'http://web:80/typo3',
33
login: {
44
admin: {
55
username: process.env.ACCESSIBILITY_BACKEND_ADMIN_USERNAME || 'admin',
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {test, expect, Locator, Page} from '@playwright/test';
2+
import config from '../config';
3+
4+
let treeRootElement: Locator;
5+
let newPageToolbarItem: Locator;
6+
7+
test.beforeEach( async ({page}) => {
8+
await page.goto(`${config.baseUrl}/module/web/layout`);
9+
await page.waitForLoadState('networkidle');
10+
11+
treeRootElement = page.locator('#typo3-pagetree-tree [identifier="apps-pagetree-root"]');
12+
newPageToolbarItem = page.locator('#typo3-pagetree-toolbar [data-tree-icon="apps-pagetree-page-default"]');
13+
})
14+
15+
test('Drag and drop new page in node without children', async ({ page }) => {
16+
const dummySiteTitle = 'Dummy page tree';
17+
await newPageToolbarItem.dragTo(treeRootElement);
18+
await page.fill('.node-edit', dummySiteTitle);
19+
await page.keyboard.press('Enter');
20+
const newPageElement = page.getByRole('treeitem', { name: dummySiteTitle });
21+
22+
await expect(newPageElement.locator('.node-name')).toHaveText(dummySiteTitle);
23+
24+
await dragDeletePage(newPageElement);
25+
});
26+
27+
test('Drag and drop new page in node with children', async ({ page }) => {
28+
const dummySiteTitle = 'Dummy page tree';
29+
await newPageToolbarItem.dragTo(treeRootElement);
30+
await page.fill('.node-edit', dummySiteTitle);
31+
await page.keyboard.press('Enter');
32+
const newRootPageElement = page.getByRole('treeitem', { name: dummySiteTitle });
33+
34+
const pageTitle = 'Dummy 1';
35+
newPageToolbarItem.dragTo(newRootPageElement);
36+
await page.fill('.node-edit', pageTitle);
37+
await page.keyboard.press('Enter');
38+
const newPageElement = page.getByRole('treeitem', { name: pageTitle });
39+
40+
await newPageElement.waitFor({ state: 'visible' });
41+
await expect(newPageElement.locator('.node-name')).toHaveText(pageTitle);
42+
43+
await dragDeletePage(newPageElement);
44+
await dragDeletePage(newRootPageElement);
45+
});
46+
47+
test('Drag and drop new page and quit page creation', async ({ page }) => {
48+
const pageTitle = 'Dummy quit creation';
49+
await newPageToolbarItem.dragTo(treeRootElement);
50+
51+
await page.fill('.node-edit', pageTitle);
52+
await page.keyboard.press('Escape');
53+
54+
await expect(page.getByRole('treeitem', { name: pageTitle })).not.toBeAttached();
55+
});
56+
57+
test('Drag and drop new page and leave page title empty', async ({ page }) => {
58+
let pageTitle = 'Dummy empty title';
59+
await newPageToolbarItem.dragTo(treeRootElement);
60+
61+
await page.fill('.node-edit', '');
62+
await page.keyboard.press('Enter');
63+
64+
await expect(page.getByRole('treeitem', { name: pageTitle })).not.toBeAttached();
65+
});
66+
67+
async function dragDeletePage(pageToDelete: Locator) {
68+
const box = await pageToDelete.boundingBox();
69+
await pageToDelete.dragTo(pageToDelete, {
70+
sourcePosition: {
71+
x: 10,
72+
y: box.height / 2,
73+
},
74+
targetPosition: {
75+
x: box.width - 10,
76+
y: box.height / 2,
77+
}
78+
});
79+
await pageToDelete.page().locator('typo3-backend-modal button[name="delete"]').click();
80+
await expect(pageToDelete).not.toBeAttached();
81+
}

0 commit comments

Comments
 (0)