diff --git a/.drone.star b/.drone.star index 82735c537c8..dec78a322db 100644 --- a/.drone.star +++ b/.drone.star @@ -585,14 +585,14 @@ def e2eTestsOnPlaywright(ctx): steps += ocisService() - steps += [{ + steps.append({ "name": "e2e-tests-playwright", "image": OC_CI_NODEJS_IMAGE, "environment": environment, "commands": [ "pnpm test:e2e:playwright --project=chromium", ], - }] + }) pipelines.append({ "kind": "pipeline", @@ -682,6 +682,16 @@ def e2eTests(ctx): "FEDERATED_BASE_URL_OCIS": "federation-ocis:9200", } + if "suites" in matrix: + environment["TEST_SUITES"] = ",".join(params["suites"]) + steps += filterTestSuitesToRun(ctx, params["suites"]) + elif "features" in matrix: + environment["FEATURE_FILES"] = ",".join(params["features"]) + steps += filterTestSuitesToRun(ctx, params["features"]) + else: + print("Error: No suites or features defined for e2e test suite '%s'" % suite) + return [] + steps += restoreBuildArtifactCache(ctx, "pnpm", ".pnpm-store") + \ installPnpm() + \ restoreBrowsersCache() + \ @@ -711,22 +721,15 @@ def e2eTests(ctx): steps += (tikaService() if params["tikaNeeded"] else []) + \ ocisService(params["extraServerEnvironment"]) - command = "bash run-e2e.sh " - if "suites" in matrix: - command += "--suites %s" % ",".join(params["suites"]) - elif "features" in matrix: - command += "%s" % " ".join(params["features"]) - else: - print("Error: No suites or features defined for e2e test suite '%s'" % suite) - return [] - steps += [{ "name": "e2e-tests", "image": OC_CI_NODEJS_IMAGE, "environment": environment, "commands": [ + "cat %s/tests/drone/suites.env" % dir["web"], + ". %s/tests/drone/suites.env || true" % dir["web"], "cd tests/e2e", - command, + "bash run-e2e.sh", ], }] + \ uploadTracingResult(ctx) + \ @@ -2009,3 +2012,21 @@ def restoreBrowsersCache(): ], }, ] + +def filterTestSuitesToRun(ctx, suites = []): + if "full-ci" in ctx.build.title.lower() and ctx.build.event == "cron": + return [] + if len(suites) and "cucumber/" in suites[0]: + ENV = "FEATURE_FILES=" + else: + ENV = "TEST_SUITES=" + return [ + { + "name": "filter-suites-to-run", + "image": OC_CI_NODEJS_IMAGE, + "commands": [ + "node %s/tests/drone/filterTestSuitesToRun.js %s" % (dir["web"], ",".join(suites)), + "cat %s/tests/drone/suites.env" % dir["web"], + ], + }, + ] diff --git a/tests/drone/filterTestSuitesToRun.js b/tests/drone/filterTestSuitesToRun.js new file mode 100644 index 00000000000..2aaeebc1201 --- /dev/null +++ b/tests/drone/filterTestSuitesToRun.js @@ -0,0 +1,155 @@ +import { execSync } from 'child_process' +import fs from 'fs' +import path from 'path' + +const targetBranch = process.env.DRONE_TARGET_BRANCH || 'master' + +// INFO: 1 and 2 elements are node and script name respectively +const scriptDir = path.dirname(process.argv[1]) +const suitesToCheck = process.argv[2] + .split(',') + .map((suite) => suite.trim()) + .filter((suite) => suite) + +// list of test suites with dependent packages +// Example: +// { +// 'web-app-ocm': ['ocm'], +// 'web-app-search': ['search'] +// } +const packageToTestSuiteMap = {} + +/* +-------------------- + web packages +-------------------- + */ +const excludePackages = ['web-container', 'web-test-helpers'] +// default packages that affect all test suites +const defaultPackages = ['web-client', 'web-runtime', 'web-pkg'] +const packagesDir = `${scriptDir}/../../packages` +const allWebPackages = fs + .readdirSync(packagesDir) + .filter( + (entry) => + entry.startsWith('web-') && + fs.statSync(path.join(packagesDir, entry)).isDirectory() && + !excludePackages.includes(entry) + ) + +/* +-------------------- + test suites +-------------------- + */ +const testSuitesDir = `${scriptDir}/../e2e/cucumber/features` +const testSuites = fs.readdirSync(testSuitesDir).filter((entry) => { + if (!fs.statSync(path.join(testSuitesDir, entry)).isDirectory()) { + return false + } + const webPackagesFile = path.join(testSuitesDir, entry, 'web-packages.txt') + if (fs.existsSync(webPackagesFile)) { + const content = fs.readFileSync(webPackagesFile, 'utf-8') + const depPackages = content.split('\n').filter((line) => line && line.startsWith('web-')) + if (depPackages.length) { + depPackages.forEach((pkg) => { + if (!(pkg in packageToTestSuiteMap)) { + packageToTestSuiteMap[pkg] = [] + } + !packageToTestSuiteMap[pkg].includes(entry) && packageToTestSuiteMap[pkg].push(entry) + }) + return true + } + } + allWebPackages.forEach((pkg) => { + if (!(pkg in packageToTestSuiteMap)) { + packageToTestSuiteMap[pkg] = [] + } + !packageToTestSuiteMap[pkg].includes(entry) && packageToTestSuiteMap[pkg].push(entry) + }) + return true +}) + +function getChangedFiles() { + const changedFiles = execSync(`git diff --name-only origin/${targetBranch} HEAD`).toString() + console.log('[INFO] Changed files:\n', changedFiles) + return [...new Set([...changedFiles.split('\n')])].filter((file) => file) +} + +function getPackageFromFile(file) { + if (!file.startsWith('packages/')) { + return + } + const packages = Object.keys(packageToTestSuiteMap) + for (const pkg of packages) { + if (file.startsWith(`packages/${pkg}`)) { + return pkg + } + } +} + +function getAffectedTestSuites(changedFiles) { + const affectedSuites = new Set() + for (const file of changedFiles) { + // run all test suites if changes are in the following paths + if ( + file.startsWith('tests/e2e/') || + file.startsWith('tests/drone/') || + file === '.drone.star' || + file === 'package.json' + ) { + // run all test suites + return testSuites + } + const packageName = getPackageFromFile(file) + if (packageName && packageName in packageToTestSuiteMap) { + packageToTestSuiteMap[packageName].forEach((suite) => affectedSuites.add(suite)) + } + } + return Array.from(affectedSuites) +} + +function createSuitesToRunEnvFile(suites = []) { + console.log('[INFO] Provided test suites/features:\n - ' + suitesToCheck.join('\n - ')) + console.log('[INFO] Test suites/features to run:\n - ' + suites.join('\n - ')) + const envContent = ['TEST_SUITES', suites.join(',')] + if (suites[0].startsWith('cucumber/')) { + envContent[0] = ['FEATURE_FILES'] + } + // create suites.env file in the same directory as the script + fs.writeFileSync(`${scriptDir}/suites.env`, envContent.join('=')) +} + +function main() { + const changedFiles = getChangedFiles() + if (changedFiles.length === 0) { + console.log('[INFO] No changes detected.') + process.exit(78) // Skip the pipeline + } + const affectedTestSuites = getAffectedTestSuites(changedFiles) + if (affectedTestSuites.length === 0) { + console.log('[INFO] No affected test suites/features to run.') + process.exit(78) // Skip the pipeline + } + if (suitesToCheck.length) { + const suitesToRun = suitesToCheck.filter((suite) => { + suite = suite.trim() + if (suite.startsWith('cucumber/')) { + suite = suite.replace('cucumber/features/', '').split('/').shift() + } + return affectedTestSuites.includes(suite) + }) + if (suitesToRun.length === 0) { + console.log( + '[INFO] The following test suites/features are not affected and will be skipped:\n - ', + suitesToCheck.join('\n - ') + ) + process.exit(78) // Skip the pipeline + } + createSuitesToRunEnvFile(suitesToRun) + return + } + createSuitesToRunEnvFile(affectedTestSuites) +} + +main() diff --git a/tests/e2e/cucumber/features/admin-settings/web-packages.txt b/tests/e2e/cucumber/features/admin-settings/web-packages.txt new file mode 100644 index 00000000000..cde7461062e --- /dev/null +++ b/tests/e2e/cucumber/features/admin-settings/web-packages.txt @@ -0,0 +1,4 @@ +# FOR CI ONLY +# Run the test suite only if the changes are in the following packages: + +web-app-admin-settings \ No newline at end of file diff --git a/tests/e2e/cucumber/features/app-provider/web-packages.txt b/tests/e2e/cucumber/features/app-provider/web-packages.txt new file mode 100644 index 00000000000..c9be93984a9 --- /dev/null +++ b/tests/e2e/cucumber/features/app-provider/web-packages.txt @@ -0,0 +1,5 @@ +# FOR CI ONLY +# Run the test suite only if the changes are in the following packages: + +web-app-external +web-app-files \ No newline at end of file diff --git a/tests/e2e/cucumber/features/app-store/web-packages.txt b/tests/e2e/cucumber/features/app-store/web-packages.txt new file mode 100644 index 00000000000..2cba49f61ac --- /dev/null +++ b/tests/e2e/cucumber/features/app-store/web-packages.txt @@ -0,0 +1,4 @@ +# FOR CI ONLY +# Run the test suite only if the changes are in the following packages: + +web-app-app-store \ No newline at end of file diff --git a/tests/e2e/cucumber/features/file-action/web-packages.txt b/tests/e2e/cucumber/features/file-action/web-packages.txt new file mode 100644 index 00000000000..2779f4a7a63 --- /dev/null +++ b/tests/e2e/cucumber/features/file-action/web-packages.txt @@ -0,0 +1,5 @@ +# FOR CI ONLY +# Run the test suite only if the changes are in the following packages: + +web-app-files +web-app-preview \ No newline at end of file diff --git a/tests/e2e/cucumber/features/ocm/web-packages.txt b/tests/e2e/cucumber/features/ocm/web-packages.txt new file mode 100644 index 00000000000..13beeec5b7d --- /dev/null +++ b/tests/e2e/cucumber/features/ocm/web-packages.txt @@ -0,0 +1,4 @@ +# FOR CI ONLY +# Run the test suite only if the changes are in the following packages: + +web-app-ocm \ No newline at end of file diff --git a/tests/e2e/run-e2e.sh b/tests/e2e/run-e2e.sh index 495a0d3ffd0..4a8c8262597 100755 --- a/tests/e2e/run-e2e.sh +++ b/tests/e2e/run-e2e.sh @@ -32,6 +32,10 @@ Available options: --total-parts - total number of groups to divide into e.g.: --total-parts 4 (suites will be divided into 4 groups) --help, -h - show cli options + +Available env variables: + TEST_SUITES - Comma separated list of suites to run. (Will be ignored if --suites is provided) + FEATURE_FILES - Comma separated list of feature files to run. (Will be ignored if feature paths are provided) " function log() { @@ -146,6 +150,13 @@ function buildSuitesPattern() { GLOB_FEATURE_PATHS="$FEATURES_DIR/$suites/**/*.feature" } +if [[ -n $TEST_SUITES ]] && [[ -z "$FILTER_SUITES" ]]; then + FILTER_SUITES=$(echo "$TEST_SUITES" | sed -E "s/,/\n/g") +fi +if [[ -n $FEATURE_FILES ]] && [[ -z "$FEATURE_PATHS_FROM_ARG" ]]; then + FEATURE_PATHS_FROM_ARG=$(echo "$FEATURE_FILES" | sed -E "s/,/ /g") +fi + # 1. [RUN E2E] run features from provided paths if [[ -n $FEATURE_PATHS_FROM_ARG && "$SKIP_RUN_PARTS" == true ]]; then getFeaturePaths "$FEATURE_PATHS_FROM_ARG"