kunai-runner 6.10.101

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.env.example +68 -0
  2. package/README.md +17 -0
  3. package/docs/01_shibboleth_auth.md +122 -0
  4. package/docs/02_versioning_and_release.md +146 -0
  5. package/docs/backlog.md +71 -0
  6. package/docs/combine_videos.md +109 -0
  7. package/docs/test_data.md +34 -0
  8. package/lib/auth-file.ts +22 -0
  9. package/lib/login-adapters/builtin.ts +42 -0
  10. package/lib/login-adapters/duo-mfa.ts +41 -0
  11. package/lib/login-adapters/incommon-seamlessaccess.ts +56 -0
  12. package/lib/login-adapters/index.ts +68 -0
  13. package/lib/login-adapters/shibboleth-direct.ts +34 -0
  14. package/lib/login-adapters/types.ts +21 -0
  15. package/package.json +51 -0
  16. package/playwright.config.ts +206 -0
  17. package/scripts/combine_videos.py +250 -0
  18. package/tests/suite/01-preflight.spec.ts +147 -0
  19. package/tests/suite/02-account-management.spec.ts +113 -0
  20. package/tests/suite/03-create-dataverse.spec.ts +114 -0
  21. package/tests/suite/04-publish-dataverse.spec.ts +33 -0
  22. package/tests/suite/05-theme-widgets.spec.ts +65 -0
  23. package/tests/suite/06-theme-widgets-edit.spec.ts +60 -0
  24. package/tests/suite/10-assign-user-group-roles.spec.ts +34 -0
  25. package/tests/suite/11-create-edit-metadata-template.spec.ts +61 -0
  26. package/tests/suite/12-create-dataverse-collection.spec.ts +27 -0
  27. package/tests/suite/13-dataset-actions.spec.ts +105 -0
  28. package/tests/suite/14-browse-dataset-records.spec.ts +32 -0
  29. package/tests/suite/15-search-dataset-records.spec.ts +26 -0
  30. package/tests/suite/16-view-dataset-version-history.spec.ts +28 -0
  31. package/tests/suite/17-download-dataset-files.spec.ts +35 -0
  32. package/tests/suite/assets/footer.png +0 -0
  33. package/tests/suite/assets/logo.png +0 -0
  34. package/tests/suite/assets/thumbnail.png +0 -0
  35. package/tests/suite/auth.setup.ts +71 -0
  36. package/tests/suite/s02-state.ts +35 -0
  37. package/tests/suite/test-data/replaced-sample-dataset-file.txt +7 -0
  38. package/tests/suite/test-data/sample-dataset-file-2.txt +7 -0
  39. package/tests/suite/test-data/sample-dataset-file.txt +7 -0
  40. package/tests/suite/tsconfig.json +10 -0
  41. package/tsconfig.json +15 -0
@@ -0,0 +1,105 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const process = (globalThis as any).process;
3
+
4
+ /**
5
+ * @tags @21cfr
6
+ *
7
+ * 21 CFR Part 11 — Tests #7–#11
8
+ * Creates a dataset, edits metadata, edits file metadata, replaces a file,
9
+ * and publishes the dataset.
10
+ */
11
+
12
+ test(
13
+ "21 CFR: Dataset actions (create, edit, replace, publish)",
14
+ { tag: ["@21cfr"] },
15
+ async ({ page }) => {
16
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
17
+
18
+ // ─── Test #7 — Create Dataset ───
19
+ await page.getByRole("button", { name: "Add Data" }).click();
20
+ await page.getByRole("link", { name: "New Dataset" }).click();
21
+ await page
22
+ .locator('[id$=":0:inputText"]')
23
+ .first()
24
+ .type("Playwright Test Dataset");
25
+ await page
26
+ .locator('[id$=":0:description"]')
27
+ .first()
28
+ .type(
29
+ "This is a dummy dataset created by Playwright for testing purposes.",
30
+ );
31
+ await page
32
+ .locator(".ui-selectcheckboxmenu-multiple-container")
33
+ .first()
34
+ .click();
35
+
36
+ await page
37
+ .locator(".ui-selectcheckboxmenu-items-wrapper")
38
+ .first()
39
+ .getByText("Chemistry")
40
+ .click();
41
+
42
+ await page
43
+ .locator('[id="datasetForm:fileUpload_input"]')
44
+ .setInputFiles([
45
+ "tests/suite/test-data/sample-dataset-file.txt",
46
+ "tests/suite/test-data/sample-dataset-file-2.txt",
47
+ ]);
48
+ await page.waitForTimeout(3000);
49
+ await page.getByRole("button", { name: "Save Dataset" }).click();
50
+
51
+ // ─── Test #8 — Edit Dataset Metadata ───
52
+ await page
53
+ .getByRole("button", { name: "Edit Dataset" })
54
+ .click({ force: true });
55
+ await page.locator("id=datasetForm:editMetadata").dispatchEvent("click");
56
+ await page
57
+ .locator('[id$=":0:inputText"]')
58
+ .first()
59
+ .fill("Playwright Test Dataset Modified");
60
+ await page.getByRole("button", { name: "Save Changes" }).last().click();
61
+
62
+ // ─── Test #9 — Edit File Metadata ───
63
+ await page.locator(".ui-chkbox-all").first().click();
64
+ await page.getByRole("button", { name: "Edit Files" }).click();
65
+ await page
66
+ .getByRole("link", { name: "Metadata" })
67
+ .last()
68
+ .click({ force: true });
69
+ await page
70
+ .locator('[name="datasetForm:filesTable:0:fileDescription"]')
71
+ .type("This is a modified description for the dataset file.");
72
+ await page.getByRole("button", { name: "Save Changes" }).last().click();
73
+
74
+ // ─── Test #10 — Replace File ───
75
+ await page.getByRole("link", { name: "sample-dataset-file.txt" }).click();
76
+ await page.getByRole("button", { name: "Edit File" }).click();
77
+ await page.getByRole("link", { name: "Replace" }).click();
78
+ await page
79
+ .locator('[id="datasetForm:fileUpload_input"]')
80
+ .setInputFiles([
81
+ "tests/suite/test-data/replaced-sample-dataset-file.txt",
82
+ ]);
83
+
84
+ const saveButton = page
85
+ .getByRole("button", { name: "Save Changes" })
86
+ .last();
87
+ if (await saveButton.isVisible()) {
88
+ await saveButton.click();
89
+ } else {
90
+ console.error(
91
+ "ERROR: Save Changes button not visible after file replacement. " +
92
+ "21 CFR Part 11 compliance may not be fully met. Proceeding with bypass.",
93
+ );
94
+ await page.getByRole("button", { name: "Done" }).click();
95
+ }
96
+
97
+ // ─── Test #11 — Publish Dataset ───
98
+ await page
99
+ .getByRole("link", { name: "Playwright Test Dataset Modified" })
100
+ .click();
101
+ await page.getByRole("link", { name: "Publish Dataset" }).click();
102
+ await page.getByRole("button", { name: "Continue" }).click();
103
+ console.log("Dataset finalized and published.");
104
+ },
105
+ );
@@ -0,0 +1,32 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const process = (globalThis as any).process;
3
+
4
+ /**
5
+ * @tags @21cfr
6
+ *
7
+ * 21 CFR Part 11 — Test #12
8
+ * Sorts the results table by name, opens the first dataset record, and
9
+ * navigates to its Metadata tab.
10
+ */
11
+
12
+ test(
13
+ "21 CFR: Browse dataset records",
14
+ { tag: ["@21cfr"] },
15
+ async ({ page }) => {
16
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
17
+ await page.getByRole("button", { name: "Sort" }).click();
18
+ await page.getByRole("link", { name: "Name" }).first().click();
19
+ // Click the "Toggle Collections" checkbox anchor to filter to datasets only.
20
+ await page.locator('a.facetTypeChBox[href*="types=datasets"]').click();
21
+ await page.waitForLoadState("domcontentloaded");
22
+ await page
23
+ .locator("#resultsTable")
24
+ .locator("tbody")
25
+ .locator("tr")
26
+ .first()
27
+ .locator("a")
28
+ .first()
29
+ .click();
30
+ await page.getByRole("link", { name: "Metadata" }).click();
31
+ },
32
+ );
@@ -0,0 +1,26 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const process = (globalThis as any).process;
3
+
4
+ /**
5
+ * @tags @21cfr
6
+ *
7
+ * 21 CFR Part 11 — Test #13
8
+ * Searches for "Playwright" using the dataverse search box, then performs
9
+ * an advanced search and asserts the URL reflects the query.
10
+ */
11
+
12
+ test(
13
+ "21 CFR: Search dataset records",
14
+ { tag: ["@21cfr"] },
15
+ async ({ page }) => {
16
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
17
+ await page.getByPlaceholder("Search this dataverse").fill("Playwright");
18
+ await page.getByRole("link", { name: "Find" }).click();
19
+ await page.getByRole("link", { name: "Advanced Search" }).click();
20
+ await page
21
+ .locator('[id="advancedSearchForm:dvFieldName"]')
22
+ .fill("Playwright");
23
+ await page.getByRole("button", { name: "Find" }).last().click();
24
+ await expect(page).toHaveURL(/Playwright/);
25
+ },
26
+ );
@@ -0,0 +1,28 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const process = (globalThis as any).process;
3
+
4
+ /**
5
+ * @tags @21cfr
6
+ *
7
+ * 21 CFR Part 11 — Test #14
8
+ * Opens the first dataset in the results table, navigates to its Versions
9
+ * tab, and clicks through to version 1.0.
10
+ */
11
+
12
+ test(
13
+ "21 CFR: View dataset version history",
14
+ { tag: ["@21cfr"] },
15
+ async ({ page }) => {
16
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
17
+ await page
18
+ .locator("#resultsTable")
19
+ .locator("tbody")
20
+ .locator("tr")
21
+ .first()
22
+ .locator("a")
23
+ .first()
24
+ .click();
25
+ await page.getByRole("link", { name: "Versions" }).click();
26
+ await page.getByRole("link", { name: "1.0" }).last().click();
27
+ },
28
+ );
@@ -0,0 +1,35 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const process = (globalThis as any).process;
3
+
4
+ /**
5
+ * @tags @21cfr
6
+ *
7
+ * 21 CFR Part 11 — Test #15
8
+ * Opens the first dataset in the results table, selects all files, triggers
9
+ * a download, and verifies the downloaded filename is non-null.
10
+ */
11
+
12
+ test(
13
+ "21 CFR: Download dataset files",
14
+ { tag: ["@21cfr"] },
15
+ async ({ page }) => {
16
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
17
+
18
+ await page
19
+ .locator("#resultsTable tbody tr")
20
+ .first()
21
+ .locator("a")
22
+ .first()
23
+ .click();
24
+
25
+ await page.locator(".ui-chkbox-all").first().click();
26
+
27
+ const downloadPromise = page.waitForEvent("download");
28
+ await page.getByRole("link", { name: "Download" }).click();
29
+ const download = await downloadPromise;
30
+
31
+ const fileName = download.suggestedFilename();
32
+ console.log(`Downloaded file: ${fileName}`);
33
+ expect(fileName).not.toBeNull();
34
+ },
35
+ );
Binary file
Binary file
Binary file
@@ -0,0 +1,71 @@
1
+ import { test as setup } from "@playwright/test";
2
+ import { getLoginAdapter } from "../../lib/login-adapters";
3
+ import { authFilePath } from "../../lib/auth-file";
4
+ import fs from "fs";
5
+
6
+ const process = (globalThis as any).process;
7
+
8
+ /**
9
+ * Reads a required environment variable, throwing a clear error if absent.
10
+ */
11
+ function requireEnv(name: string): string {
12
+ const value = process.env[name];
13
+ if (!value) {
14
+ throw new Error(
15
+ `Missing required environment variable "${name}". ` +
16
+ `Add it to your .env file (see .env.example).`,
17
+ );
18
+ }
19
+ return value;
20
+ }
21
+
22
+ setup("Authenticate", async ({ page, browserName }) => {
23
+ // Each browser gets its own auth file so sessions never collide.
24
+ const authFile = authFilePath(browserName);
25
+
26
+ if (fs.existsSync(authFile)) {
27
+ await page
28
+ .context()
29
+ .addCookies(JSON.parse(fs.readFileSync(authFile, "utf-8")).cookies);
30
+ }
31
+
32
+ await page.goto("/");
33
+
34
+ // Dismiss cookie consent banner if present
35
+ const cookieConsent = page.locator('button[data-role="all"]');
36
+ if (await cookieConsent.isVisible()) {
37
+ await cookieConsent.click();
38
+ }
39
+
40
+ // Check for the user's full name inside the navbar user display element.
41
+ // Scoping to #userDisplayInfoTitle prevents false positives when the name
42
+ // appears in dataset titles or other page content.
43
+ const fullName = requireEnv("DV_FULL_NAME");
44
+ const fullNameVisible = await page
45
+ .locator("#userDisplayInfoTitle")
46
+ .getByText(fullName, { exact: false })
47
+ .isVisible()
48
+ .catch(() => false);
49
+
50
+ if (fullNameVisible) {
51
+ console.log("Already logged in. Skipping authentication.");
52
+ return;
53
+ }
54
+
55
+ console.log("Not logged in. Starting authentication via adapter.");
56
+
57
+ // Use .first() — some instances render two "Log In" links (navbar + mega-menu).
58
+ const loginButton = page
59
+ .getByRole("link", { name: "Log In", exact: true })
60
+ .first();
61
+ await loginButton.click({ force: true });
62
+
63
+ const adapter = getLoginAdapter();
64
+ await adapter.login(page, {
65
+ username: requireEnv("DV_USERNAME"),
66
+ password: requireEnv("DV_PASSWORD"),
67
+ });
68
+
69
+ fs.mkdirSync("playwright/.auth", { recursive: true });
70
+ await page.context().storageState({ path: authFile });
71
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Tiny persistence helper for the Section 2 dataverse identifier.
3
+ *
4
+ * The identifier is written to a local temp file that is git-ignored.
5
+ * This lets the create-dataverse test write the identifier once and all
6
+ * subsequent Section 2 tests (publish, theme, etc.) consume it even when
7
+ * run independently.
8
+ */
9
+ import fs from "fs";
10
+ import path from "path";
11
+
12
+ const STATE_FILE = path.resolve(process.cwd(), ".s2-dataverse-id");
13
+
14
+ /**
15
+ * Persist the dataverse identifier to disk so later tests can consume it.
16
+ * Called once at the end of 05-create-dataverse.
17
+ */
18
+ export function writeDataverseId(id: string): void {
19
+ fs.writeFileSync(STATE_FILE, id, "utf-8");
20
+ }
21
+
22
+ /**
23
+ * Read the dataverse identifier written by 05-create-dataverse.
24
+ * Throws a helpful error if the file doesn't exist (i.e. the create test
25
+ * has never run in this workspace).
26
+ */
27
+ export function readDataverseId(): string {
28
+ if (!fs.existsSync(STATE_FILE)) {
29
+ throw new Error(
30
+ `Dataverse identifier state file not found at:\n ${STATE_FILE}\n` +
31
+ `Run 05-create-dataverse.spec.ts first to generate it.`,
32
+ );
33
+ }
34
+ return fs.readFileSync(STATE_FILE, "utf-8").trim();
35
+ }
@@ -0,0 +1,7 @@
1
+ This is a REPLACED sample dataset file used by Playwright to test and generate datasets.
2
+
3
+ You can verify the MD5 of this file as needed using node.js crypto. It is recommended NOT to use MD5 due to collision vulnerabilities.
4
+
5
+ Going forward, you may prefer to use SHA3-384 or SHA3-512 or Argon2id.
6
+
7
+ Please refer to the latest NIST requirements about hashing.
@@ -0,0 +1,7 @@
1
+ This is a sample dataset file number 2 used by Playwright to test and generate datasets.
2
+
3
+ You can verify the MD5 of this file as needed using node.js crypto. It is recommended NOT to use MD5 due to collision vulnerabilities.
4
+
5
+ Going forward, you may prefer to use SHA3-384 or SHA3-512 or Argon2id.
6
+
7
+ Please refer to the latest NIST requirements about hashing.
@@ -0,0 +1,7 @@
1
+ This is a sample dataset file used by Playwright to test and generate datasets.
2
+
3
+ You can verify the MD5 of this file as needed using node.js crypto. It is recommended NOT to use MD5 due to collision vulnerabilities.
4
+
5
+ Going forward, you may prefer to use SHA3-384 or SHA3-512 or Argon2id.
6
+
7
+ Please refer to the latest NIST requirements about hashing.
@@ -0,0 +1,10 @@
1
+ {
2
+ /**
3
+ * Unified suite TypeScript config.
4
+ */
5
+ "extends": "../../tsconfig.json",
6
+ "compilerOptions": {
7
+ "rootDir": "../..",
8
+ "baseUrl": "../.."
9
+ }
10
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "resolveJsonModule": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "dist",
11
+ "ignoreDeprecations": "6.0",
12
+ "types": ["node"]
13
+ },
14
+ "include": ["playwright.config.ts", "tests/**/*.ts", "lib/**/*.ts"]
15
+ }