diff --git a/tools/playwright/.gitignore b/tools/playwright/.gitignore new file mode 100644 index 0000000..413f06a --- /dev/null +++ b/tools/playwright/.gitignore @@ -0,0 +1,8 @@ +# Playwright +node_modules/ +blob-report/ +test-results/ +playwright-report/ +playwright/.cache/ +playwright/.auth/ +downloads/ diff --git a/tools/playwright/package-lock.json b/tools/playwright/package-lock.json new file mode 100644 index 0000000..759b1f6 --- /dev/null +++ b/tools/playwright/package-lock.json @@ -0,0 +1,112 @@ +{ + "name": "playwright", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "playwright", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^17.4.2" + }, + "devDependencies": { + "@playwright/test": "^1.60.0", + "@types/node": "^25.8.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", + "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/tools/playwright/package.json b/tools/playwright/package.json new file mode 100644 index 0000000..d66fe31 --- /dev/null +++ b/tools/playwright/package.json @@ -0,0 +1,20 @@ +{ + "name": "playwright-export-module", + "version": "0.0.1", + "description": "Export elixForms module into .elx JSON file using Playwright", + "main": "index.js", + "scripts": { + "export": "npx playwright test export-module.spec.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@playwright/test": "^1.60.0", + "@types/node": "^25.8.0" + }, + "dependencies": { + "dotenv": "^17.4.2" + } +} diff --git a/tools/playwright/playwright.config.ts b/tools/playwright/playwright.config.ts new file mode 100644 index 0000000..3ee6fd0 --- /dev/null +++ b/tools/playwright/playwright.config.ts @@ -0,0 +1,45 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +import dotenv from 'dotenv'; +import path from 'path'; +dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* 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: 'http://localhost:3000', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + //trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/tools/playwright/tests/export-module.spec.ts b/tools/playwright/tests/export-module.spec.ts new file mode 100644 index 0000000..3bda361 --- /dev/null +++ b/tools/playwright/tests/export-module.spec.ts @@ -0,0 +1,75 @@ +import { test, expect } from '@playwright/test'; + +test('export module', async ({ page, context }) => { + // Override with environment variables, if they exist + const username = process.env.ELIXFORMS_USERNAME || null; + const password = process.env.ELIXFORMS_PASSWORD || null; + const moduleTag = process.env.ELIXFORMS_MODULE_TAG || null; + // Validate that all required environment variables are set + if (!username || !password || !moduleTag) { + throw new Error('Please set ELIXFORMS_USERNAME, ELIXFORMS_PASSWORD, and ELIXFORMS_MODULE_TAG using one of:\n- an .env file in the project root\n- specify an .env file with `npx env-cmd --file <.env_path> -- `\n- pass them as environment variables'); + } + + test.setTimeout(60_000); // Set a longer timeout for this test + + await page.goto('https://console-unipr.elixforms.it/backoffice/'); + + let loginButton = page.getByRole('button', { name: 'Accedi' }); + await expect(loginButton).toBeVisible(); + + // Write username and password + await page.locator('#username').fill(username); + await page.locator('#password').fill(password); + // Click login button + await loginButton.click(); + + await expect(page.getByText('Benvenuto')).toBeVisible(); + + await page.goto('https://console-unipr.elixforms.it/rwe2/admin_console.jsp'); + + let moduleSearchTextbox = page.locator('#TITLE__TAG__CATTAG'); + await expect(moduleSearchTextbox).toBeVisible(); + await moduleSearchTextbox.clear(); + await moduleSearchTextbox.fill(moduleTag); + await moduleSearchTextbox.press('Enter'); + + await page.waitForTimeout(2000); + + let firstResultButton = page + .locator('//div[contains(@class, "item")][1]//input[starts-with(@name, "INSERT_BTN_USER_")]'); + await expect(firstResultButton).toBeVisible(); + + await firstResultButton.click(); + + // Start waiting for new page before clicking. Note no await. + const pagePromise = context.waitForEvent('page'); + + let exportLink = page.getByText('Esporta il modulo'); + await expect(exportLink).toBeVisible(); + await exportLink.click(); + + const newPage = await pagePromise; // Wait for the new page to open. + await newPage.waitForLoadState(); + await newPage.bringToFront(); + + let moduleProtocolCheckbox = newPage.locator('#moduleProtocol'); + await expect(moduleProtocolCheckbox).toBeVisible(); + await moduleProtocolCheckbox.click(); + + let downloadButton = newPage.locator('#download'); + await expect(downloadButton).toBeVisible(); + await downloadButton.click(); + + await page.waitForTimeout(5000); + + let downloadLink = newPage.getByRole('link', { name: 'Scarica il modulo' }); + await expect(downloadLink).toBeVisible(); + + // Start waiting for download before clicking. Note no await. + const downloadPromise = newPage.waitForEvent('download'); + await downloadLink.click(); + const download = await downloadPromise; + + // Wait for the download process to complete and save the downloaded file somewhere. + await download.saveAs('./downloads/' + download.suggestedFilename()); +});