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,147 @@
1
+ import { test, expect } from "@playwright/test";
2
+ const process = (globalThis as any).process;
3
+
4
+ /**
5
+ * @tags @standard
6
+ *
7
+ * Preflight health-checks for a standard UNC Dataverse instance.
8
+ * Verifies the home page, header nav, and footer before any auth runs.
9
+ * Skip this test (--grep-invert @standard) when targeting a 21 CFR instance.
10
+ */
11
+
12
+ async function followLinkInPlace(
13
+ page: import("@playwright/test").Page,
14
+ linkLocator: import("@playwright/test").Locator,
15
+ assertions: (page: import("@playwright/test").Page) => Promise<void>,
16
+ ) {
17
+ const href = await linkLocator.getAttribute("href");
18
+ if (!href) throw new Error("Link has no href");
19
+ const from = page.url();
20
+ await page.goto(href);
21
+ await page.waitForLoadState("domcontentloaded");
22
+ await assertions(page);
23
+ await page.goto(from);
24
+ await page.waitForLoadState("domcontentloaded");
25
+ }
26
+
27
+ test("Preflight", { tag: ["@standard"] }, async ({ page }) => {
28
+ if (process.env.SKIP_PREFLIGHT === "true") return;
29
+ await page.goto("/");
30
+
31
+ // ─────────────────────────────────────────────
32
+ // HEADER
33
+ // ─────────────────────────────────────────────
34
+ const navbar = page.locator("#navbarFixed");
35
+
36
+ // 1. UNC Dataverse logo loads and is visible
37
+ const logo = navbar.locator('img[alt="UNC Dataverse homepage"].custom-logo');
38
+ await expect(logo).toBeVisible();
39
+ const logoLoaded = await logo.evaluate(
40
+ (img: HTMLImageElement) => img.naturalWidth > 0,
41
+ );
42
+ expect(logoLoaded).toBe(true);
43
+
44
+ // 2. Search returns results for "unc"
45
+ await navbar.getByRole("link", { name: "Search" }).click();
46
+ await navbar.locator("#navbarsearch").type("unc");
47
+ await navbar
48
+ .locator('button[type="submit"][aria-labelledby="searchNavLabel"]')
49
+ .click();
50
+ await expect(page).toHaveURL(/\/dataverse\/unc\?q=unc/);
51
+ const rows = page.locator("#searchResults #dv-main #resultsTable tbody tr");
52
+ await expect(rows.first()).toBeVisible();
53
+ expect(await rows.count()).toBeGreaterThan(0);
54
+ await page.goto("/");
55
+
56
+ // 3. About → tdx.unc.edu, mentions "Dataverse"
57
+ await followLinkInPlace(
58
+ page,
59
+ navbar.getByRole("link", { name: "About" }),
60
+ async (p) => {
61
+ await expect(p).toHaveURL(/tdx\.unc\.edu/);
62
+ await expect(p.getByText(/Dataverse/i).first()).toBeVisible();
63
+ },
64
+ );
65
+
66
+ // 4. User Guide → guides.dataverse.org, heading "User Guide" visible
67
+ await followLinkInPlace(
68
+ page,
69
+ navbar.getByRole("link", { name: "User Guide" }),
70
+ async (p) => {
71
+ await expect(p).toHaveURL(/guides\.dataverse\.org/);
72
+ await expect(
73
+ p.getByRole("heading", { name: "User Guide" }),
74
+ ).toBeVisible();
75
+ },
76
+ );
77
+
78
+ // 5. Support → tdx.unc.edu, mentions "Dataverse"
79
+ await followLinkInPlace(
80
+ page,
81
+ navbar.getByRole("link", { name: "Support" }),
82
+ async (p) => {
83
+ await expect(p).toHaveURL(/tdx\.unc\.edu/);
84
+ await expect(p.getByText(/Dataverse/i).first()).toBeVisible();
85
+ },
86
+ );
87
+
88
+ // ─────────────────────────────────────────────
89
+ // FOOTER
90
+ // ─────────────────────────────────────────────
91
+ const footer = page.locator(".custom-footer");
92
+
93
+ // 1. RDMC logo loads and is visible
94
+ const footerLogo = footer.locator('img[alt="UNC Dataverse"]');
95
+ await expect(footerLogo).toBeVisible();
96
+ const footerLogoLoaded = await footerLogo.evaluate(
97
+ (img: HTMLImageElement) => img.naturalWidth > 0,
98
+ );
99
+ expect(footerLogoLoaded).toBe(true);
100
+
101
+ // 2. UNC Dataverse User Guide → tdx.unc.edu, has word "Guide"
102
+ await followLinkInPlace(
103
+ page,
104
+ footer.getByRole("link", { name: "UNC Dataverse User Guide" }),
105
+ async (p) => {
106
+ await expect(p).toHaveURL(/tdx\.unc\.edu/);
107
+ await expect(p.getByText(/Guide/i).first()).toBeVisible();
108
+ },
109
+ );
110
+
111
+ // 3. UNC Dataverse Terms → tdx.unc.edu, has "Terms of Use"
112
+ await followLinkInPlace(
113
+ page,
114
+ footer.getByRole("link", { name: "UNC Dataverse Terms" }),
115
+ async (p) => {
116
+ await expect(p).toHaveURL(/tdx\.unc\.edu/);
117
+ await expect(p.getByText(/Terms of Use/i).first()).toBeVisible();
118
+ },
119
+ );
120
+
121
+ // 4. RDMC Data Services → URL contains "basic" & "services"
122
+ await followLinkInPlace(
123
+ page,
124
+ footer.getByRole("link", { name: "RDMC Data Services" }),
125
+ async (p) => {
126
+ const url = p.url().toLowerCase();
127
+ expect(url).toContain("basic");
128
+ expect(url).toContain("services");
129
+ await expect(p.getByText(/Service/i).first()).toBeVisible();
130
+ },
131
+ );
132
+
133
+ // 5. Submit via RDMC service portal → tdx.unc.edu, has "RDMC"
134
+ await followLinkInPlace(
135
+ page,
136
+ footer.getByRole("link", { name: /Submit via RDMC service portal/i }),
137
+ async (p) => {
138
+ await expect(p).toHaveURL(/tdx\.unc\.edu/);
139
+ await expect(p.getByText(/RDMC/i).first()).toBeVisible();
140
+ },
141
+ );
142
+
143
+ // 6. Email anchor has a mailto: href (no navigation needed)
144
+ const emailLink = footer.getByRole("link", { name: /Email/i });
145
+ const emailHref = await emailLink.getAttribute("href");
146
+ expect(emailHref?.toLowerCase()).toBe("mailto:rdmcarchive@unc.edu");
147
+ });
@@ -0,0 +1,113 @@
1
+ import { test, expect } from "@playwright/test";
2
+
3
+ /**
4
+ * @tags @standard
5
+ *
6
+ * Section 1, Test #2 — Account Management
7
+ * Exercises My Data, Notifications, Account Information, and API Token
8
+ * from the authenticated user dropdown.
9
+ */
10
+
11
+ test(
12
+ "Section 1: Account Management",
13
+ { tag: ["@standard"] },
14
+ async ({ page }) => {
15
+ await page.goto("/");
16
+
17
+ const userMenuTrigger = page.locator("span#userDisplayInfoTitle");
18
+
19
+ // ─────────────────────────────────────────────
20
+ // 1. My Data
21
+ // ─────────────────────────────────────────────
22
+ await userMenuTrigger.click();
23
+ const dropdownMenu = page
24
+ .locator("ul.dropdown-menu")
25
+ .filter({ has: page.getByRole("link", { name: "Log Out" }) });
26
+ await expect(dropdownMenu).toBeVisible();
27
+
28
+ await dropdownMenu.getByRole("link", { name: "My Data" }).click();
29
+ await expect(page).toHaveURL(
30
+ /dataverseuser\.xhtml\?selectTab=dataRelatedToMe/,
31
+ );
32
+
33
+ const cardResults = page.locator("#resultsTable #div-card-results");
34
+ await expect(cardResults).toBeVisible();
35
+ const subDivs = cardResults.locator("> div");
36
+ expect(await subDivs.count()).toBeGreaterThan(0);
37
+
38
+ await page.goto("/");
39
+ await userMenuTrigger.click();
40
+ await expect(dropdownMenu).toBeVisible();
41
+
42
+ // ─────────────────────────────────────────────
43
+ // 2. Notifications
44
+ // ─────────────────────────────────────────────
45
+ await dropdownMenu.getByRole("link", { name: "Notifications" }).click();
46
+ await expect(page).toHaveURL(
47
+ /dataverseuser\.xhtml\?selectTab=notifications/,
48
+ );
49
+ await expect(
50
+ page.getByRole("link", { name: "Notifications" }).first(),
51
+ ).toBeVisible();
52
+
53
+ await page.goto("/");
54
+ await userMenuTrigger.click();
55
+ await expect(dropdownMenu).toBeVisible();
56
+
57
+ // ─────────────────────────────────────────────
58
+ // 3. Account Information
59
+ // ─────────────────────────────────────────────
60
+ await dropdownMenu
61
+ .getByRole("link", { name: "Account Information" })
62
+ .click();
63
+ await expect(page).toHaveURL(/dataverseuser\.xhtml\?selectTab=accountInfo/);
64
+
65
+ const metadataTable = page.locator("table.metadata");
66
+ await expect(metadataTable).toBeVisible();
67
+
68
+ for (const heading of ["Username", "Given Name", "Family Name", "Email"]) {
69
+ await expect(
70
+ metadataTable.getByRole("rowheader", { name: heading }),
71
+ ).toBeVisible();
72
+ }
73
+
74
+ await expect(page.getByText(/Verified/i).first()).toBeVisible();
75
+
76
+ await page.goto("/");
77
+ await userMenuTrigger.click();
78
+ await expect(dropdownMenu).toBeVisible();
79
+
80
+ // ─────────────────────────────────────────────
81
+ // 4. API Token
82
+ // ─────────────────────────────────────────────
83
+ await dropdownMenu.getByRole("link", { name: "API Token" }).click();
84
+ await expect(page).toHaveURL(/dataverseuser\.xhtml\?selectTab=apiTokenTab/);
85
+
86
+ const createTokenBtn = page.getByRole("button", { name: "Create Token" });
87
+ await expect(createTokenBtn).toBeVisible();
88
+ await createTokenBtn.click();
89
+ await page.waitForTimeout(1000);
90
+
91
+ const tokenCode = page.locator("#apiToken pre code");
92
+ await expect(tokenCode).toBeVisible();
93
+ const tokenText = await tokenCode.textContent();
94
+ expect(tokenText).toMatch(/[a-zA-Z]/);
95
+ expect(tokenText).toMatch(/[0-9]/);
96
+ expect(tokenText).toContain("-");
97
+
98
+ await expect(page.getByText(/Expiration Date/i).first()).toBeVisible();
99
+
100
+ const expirationTd = page
101
+ .locator("table.metadata tbody tr")
102
+ .filter({ has: page.locator("th", { hasText: "Expiration Date" }) })
103
+ .locator("td");
104
+ const expirationText = await expirationTd.textContent();
105
+ const currentYear = new Date().getFullYear();
106
+ expect(expirationText).toContain(String(currentYear + 1));
107
+
108
+ await page.getByRole("button", { name: "Revoke Token" }).click();
109
+ await expect(createTokenBtn).toBeVisible();
110
+
111
+ await page.goto("/");
112
+ },
113
+ );
@@ -0,0 +1,114 @@
1
+ import { test, expect } from "@playwright/test";
2
+ import { writeDataverseId } from "./s02-state";
3
+ const process = (globalThis as any).process;
4
+
5
+ /**
6
+ * @tags @standard
7
+ *
8
+ * Section 2, Test #1 — Dataverse Creation
9
+ * Creates a new child dataverse under ROOT_DATAVERSE and persists its
10
+ * identifier for downstream tests in this section.
11
+ */
12
+
13
+ test(
14
+ "Section 2: Create Dataverse",
15
+ { tag: ["@standard"] },
16
+ async ({ page }) => {
17
+ const suffix = Date.now().toString(36);
18
+ const dataverseIdentifier = `gorilla-tiger-monkey-${suffix}`;
19
+
20
+ const rootDataverse = process.env.ROOT_DATAVERSE ?? "/dataverse/unc";
21
+ await page.goto(rootDataverse);
22
+
23
+ await page.getByRole("button", { name: "Add Data" }).click();
24
+ await page.getByRole("link", { name: "New Dataverse" }).click();
25
+ await page.waitForLoadState("domcontentloaded");
26
+
27
+ // ─── Metadata Fields ───
28
+ const metadataRootChk = page.locator('[id="dataverseForm:metadataRoot"]');
29
+ const optionBlock = page.locator('[id="dataverseForm:optionBlock"]');
30
+
31
+ await expect(metadataRootChk).toBeChecked();
32
+
33
+ const checkboxDivs = optionBlock.locator("div.checkbox");
34
+ const labelCount = await checkboxDivs.count();
35
+ expect(labelCount).toBeGreaterThan(0);
36
+
37
+ for (let i = 0; i < labelCount; i++) {
38
+ await expect(
39
+ checkboxDivs.nth(i).locator("label input[type='checkbox']"),
40
+ ).toHaveAttribute("disabled");
41
+ }
42
+
43
+ await metadataRootChk.uncheck();
44
+
45
+ await expect(
46
+ checkboxDivs.nth(0).locator("label input[type='checkbox']"),
47
+ ).toHaveAttribute("disabled");
48
+ for (let i = 1; i < labelCount; i++) {
49
+ await expect(
50
+ checkboxDivs.nth(i).locator("label input[type='checkbox']"),
51
+ ).not.toHaveAttribute("disabled");
52
+ }
53
+
54
+ await metadataRootChk.check();
55
+
56
+ const metaContinueBtn = page
57
+ .locator("button:visible")
58
+ .filter({ has: page.locator("span", { hasText: "Continue" }) });
59
+ await expect(metaContinueBtn).toBeVisible();
60
+ await metaContinueBtn.click();
61
+
62
+ for (let i = 0; i < labelCount; i++) {
63
+ await expect(
64
+ checkboxDivs.nth(i).locator("label input[type='checkbox']"),
65
+ ).toHaveAttribute("disabled");
66
+ }
67
+
68
+ // ─── Browse/Search Facets ───
69
+ const facetsRoot = page.locator('[id="dataverseForm:facetsRoot"]');
70
+ const editFacets = page.locator('[id="dataverseForm:editFacets"]');
71
+ const picklistHeaderInner = editFacets
72
+ .locator("div.ui-picklist-list-wrapper div.ui-widget-header > div")
73
+ .first();
74
+
75
+ await expect(facetsRoot).toBeChecked();
76
+ await expect(picklistHeaderInner).toHaveClass(/ui-state-disabled/);
77
+
78
+ await facetsRoot.uncheck();
79
+ await expect(picklistHeaderInner).not.toHaveClass(/ui-state-disabled/);
80
+
81
+ await facetsRoot.check();
82
+ await expect(picklistHeaderInner).toHaveClass(/ui-state-disabled/);
83
+
84
+ // ─── Fill description ───
85
+ await page
86
+ .locator('[id="dataverseForm:description"]')
87
+ .fill(
88
+ 'Welcome to <a href="https://researchdata.unc.edu/"><b>RDMC</b></a>',
89
+ );
90
+
91
+ // ─── Fill identifier ───
92
+ await page
93
+ .locator('[id="dataverseForm:identifier"]')
94
+ .fill(dataverseIdentifier);
95
+
96
+ // ─── Category ───
97
+ await page
98
+ .locator('[id="dataverseForm:dataverseCategory"]')
99
+ .selectOption("DEPARTMENT");
100
+
101
+ // ─── Create ───
102
+ await page.getByRole("button", { name: "Create Dataverse" }).click();
103
+ await page.waitForLoadState("domcontentloaded");
104
+
105
+ await expect(page).toHaveURL(
106
+ new RegExp(`/dataverse/${dataverseIdentifier}`),
107
+ );
108
+ await expect(
109
+ page.locator('a[data-original-title="Email Dataverse Contact"]'),
110
+ ).toBeVisible();
111
+
112
+ writeDataverseId(dataverseIdentifier);
113
+ },
114
+ );
@@ -0,0 +1,33 @@
1
+ import { test, expect } from "@playwright/test";
2
+ import { readDataverseId } from "./s02-state";
3
+
4
+ /**
5
+ * @tags @standard
6
+ *
7
+ * Section 2, Test #2 — Publish Dataverse
8
+ * Publishes the dataverse created in test 03.
9
+ */
10
+
11
+ test(
12
+ "Section 2: Publish Dataverse",
13
+ { tag: ["@standard"] },
14
+ async ({ page }) => {
15
+ const dataverseIdentifier = readDataverseId();
16
+
17
+ await page.goto(`/dataverse/${dataverseIdentifier}`);
18
+ await page.waitForLoadState("domcontentloaded");
19
+
20
+ await page.getByRole("button", { name: "Publish" }).click();
21
+
22
+ const publishContinueBtn = page
23
+ .locator("button:visible")
24
+ .filter({ has: page.locator("span", { hasText: "Continue" }) });
25
+ await expect(publishContinueBtn).toBeVisible();
26
+ await publishContinueBtn.click();
27
+
28
+ const successAlert = page.locator("div.alert.alert-success");
29
+ await expect(successAlert).toBeVisible();
30
+ await expect(successAlert).toContainText("Success!");
31
+ await expect(successAlert).toContainText("Your dataverse is now public.");
32
+ },
33
+ );
@@ -0,0 +1,65 @@
1
+ import { test, expect } from "@playwright/test";
2
+ import { readDataverseId } from "./s02-state";
3
+ import path from "path";
4
+
5
+ /**
6
+ * @tags @standard
7
+ *
8
+ * Section 2, Test #3 — Theme + Widgets (initial setup)
9
+ * Uploads logo, thumbnail, footer images and sets tagline/website URL.
10
+ */
11
+
12
+ test(
13
+ "Section 2: Theme + Widgets – initial setup",
14
+ { tag: ["@standard"] },
15
+ async ({ page }) => {
16
+ const dataverseIdentifier = readDataverseId();
17
+
18
+ await page.goto(`/dataverse/${dataverseIdentifier}`);
19
+ await page.waitForLoadState("domcontentloaded");
20
+
21
+ await page.getByRole("button", { name: /Edit/i }).click();
22
+ await page.getByRole("link", { name: "Theme + Widgets" }).click();
23
+ await page.waitForLoadState("domcontentloaded");
24
+
25
+ // Upload logo.png
26
+ await page
27
+ .locator('[id="themeWidgetsForm:themeWidgetsTabView:uploadlogo_input"]')
28
+ .setInputFiles(path.join(__dirname, "assets", "logo.png"));
29
+
30
+ // Upload thumbnail.png
31
+ await page
32
+ .locator(
33
+ '[id="themeWidgetsForm:themeWidgetsTabView:uploadlogoThumbnail_input"]',
34
+ )
35
+ .setInputFiles(path.join(__dirname, "assets", "thumbnail.png"));
36
+
37
+ // Upload footer.png
38
+ await page
39
+ .locator(
40
+ '[id="themeWidgetsForm:themeWidgetsTabView:uploadlogoFooter_input"]',
41
+ )
42
+ .setInputFiles(path.join(__dirname, "assets", "footer.png"));
43
+
44
+ // Tagline
45
+ await page
46
+ .locator('[id="themeWidgetsForm:themeWidgetsTabView:tagline"]')
47
+ .fill(
48
+ "Please click here to find more details about this automated testing",
49
+ );
50
+
51
+ // Website URL
52
+ await page
53
+ .locator('[id="themeWidgetsForm:themeWidgetsTabView:website"]')
54
+ .fill("https://www.youtube.com/watch?v=2qBlE2-WL60");
55
+
56
+ await page.getByRole("button", { name: "Save Changes" }).click();
57
+
58
+ const successAlert = page.locator("div.alert.alert-success");
59
+ await expect(successAlert).toBeVisible();
60
+ await expect(successAlert).toContainText("Success!");
61
+ await expect(successAlert).toContainText(
62
+ "You have successfully updated the theme for this dataverse!",
63
+ );
64
+ },
65
+ );
@@ -0,0 +1,60 @@
1
+ import { test, expect } from "@playwright/test";
2
+ import { readDataverseId } from "./s02-state";
3
+ import path from "path";
4
+
5
+ /**
6
+ * @tags @standard
7
+ *
8
+ * Section 2, Test #4 — Theme + Widgets (remove & re-upload)
9
+ * Removes the second uploaded image then re-uploads a replacement thumbnail.
10
+ */
11
+
12
+ test(
13
+ "Section 2: Theme + Widgets – remove and re-upload",
14
+ { tag: ["@standard"] },
15
+ async ({ page }) => {
16
+ const dataverseIdentifier = readDataverseId();
17
+
18
+ await page.goto(`/dataverse/${dataverseIdentifier}`);
19
+ await page.waitForLoadState("domcontentloaded");
20
+
21
+ // ─── Remove second image, Save ───
22
+ await page.getByRole("button", { name: /Edit/i }).click();
23
+ await page.getByRole("link", { name: "Theme + Widgets" }).click();
24
+ await page.waitForLoadState("domcontentloaded");
25
+
26
+ const removeButtons = page
27
+ .locator("button:visible")
28
+ .filter({ has: page.locator("span", { hasText: "Remove" }) });
29
+ await removeButtons.nth(1).click();
30
+
31
+ await page.getByRole("button", { name: "Save Changes" }).click();
32
+
33
+ const successAlert1 = page.locator("div.alert.alert-success");
34
+ await expect(successAlert1).toBeVisible();
35
+ await expect(successAlert1).toContainText("Success!");
36
+ await expect(successAlert1).toContainText(
37
+ "You have successfully updated the theme for this dataverse!",
38
+ );
39
+
40
+ // ─── Re-upload thumbnail, Save ───
41
+ await page.getByRole("button", { name: /Edit/i }).click();
42
+ await page.getByRole("link", { name: "Theme + Widgets" }).click();
43
+ await page.waitForLoadState("domcontentloaded");
44
+
45
+ await page
46
+ .locator(
47
+ '[id="themeWidgetsForm:themeWidgetsTabView:uploadlogoThumbnail_input"]',
48
+ )
49
+ .setInputFiles(path.join(__dirname, "assets", "logo.png"));
50
+
51
+ await page.getByRole("button", { name: "Save Changes" }).click();
52
+
53
+ const successAlert2 = page.locator("div.alert.alert-success");
54
+ await expect(successAlert2).toBeVisible();
55
+ await expect(successAlert2).toContainText("Success!");
56
+ await expect(successAlert2).toContainText(
57
+ "You have successfully updated the theme for this dataverse!",
58
+ );
59
+ },
60
+ );
@@ -0,0 +1,34 @@
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 #2
8
+ * Assign authenticated users a role on the root dataverse collection, then
9
+ * remove that role assignment.
10
+ */
11
+
12
+ test(
13
+ "21 CFR: Assign user group roles",
14
+ { tag: ["@21cfr"] },
15
+ async ({ page }) => {
16
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
17
+ await page.getByText("Edit").click();
18
+ await page.getByText("Permissions").click();
19
+ await page.getByText("Users/Groups All the users").click();
20
+ await page.getByText("Assign Roles to Users/Groups").click();
21
+ await page
22
+ .getByPlaceholder("Enter User/Group Name")
23
+ .type(":authenticated-users");
24
+ await page.waitForTimeout(2000);
25
+ await page.keyboard.press("Enter");
26
+ await page.locator("span.ui-radiobutton-icon").last().click();
27
+ await page.getByRole("button", { name: "Save Changes" }).click();
28
+ await page
29
+ .getByRole("gridcell", { name: /Remove Assigned Role/ })
30
+ .last()
31
+ .click();
32
+ await page.getByText(/Continue/).click();
33
+ },
34
+ );
@@ -0,0 +1,61 @@
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 #3 & #4
8
+ * Creates a dataset template, edits its name, then deletes it.
9
+ */
10
+
11
+ test(
12
+ "21 CFR: Create and edit metadata template",
13
+ { tag: ["@21cfr"] },
14
+ async ({ page }) => {
15
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
16
+ await page.getByText("Edit").click();
17
+ await page.getByText("Dataset Templates").click();
18
+ await page.getByText("Create Dataset Template").first().click();
19
+ await page
20
+ .locator('[id$=":templateName"]')
21
+ .type("Playwright Test Template");
22
+ const templateFormLocators = await page.locator('[id$=":inputText"]').all();
23
+ await templateFormLocators[0].type("Test Citation");
24
+ await templateFormLocators[6].type("Playwright Auto Tester");
25
+ await templateFormLocators[11].type("tester-dummy@unc.edu");
26
+ await page
27
+ .locator('[id$=":description"]')
28
+ .first()
29
+ .type(
30
+ "This is a dummy template created by Playwright for testing purposes.",
31
+ );
32
+
33
+ await page
34
+ .locator(".ui-selectcheckboxmenu-multiple-container")
35
+ .first()
36
+ .click();
37
+
38
+ await page
39
+ .locator(".ui-selectcheckboxmenu-items-wrapper")
40
+ .first()
41
+ .getByText("Chemistry")
42
+ .click();
43
+
44
+ await page.getByRole("button", { name: "Save + Add Terms" }).click();
45
+ await page.getByRole("button", { name: "Save Dataset Template" }).click();
46
+
47
+ await page.getByRole("button", { name: "Edit Template" }).first().click();
48
+ await page.getByRole("link", { name: "Metadata" }).click();
49
+ await page
50
+ .locator('[id$=":templateName"]')
51
+ .fill("Playwright Test Template II");
52
+ await page.getByRole("button", { name: "Save Changes" }).click();
53
+
54
+ await page
55
+ .locator(".btn-group")
56
+ .first()
57
+ .locator('[data-original-title="Delete"]')
58
+ .click();
59
+ await page.getByRole("button", { name: "Continue" }).click();
60
+ },
61
+ );
@@ -0,0 +1,27 @@
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 #6
8
+ * Creates a new child dataverse collection then deletes it.
9
+ */
10
+
11
+ test(
12
+ "21 CFR: Create dataverse collection",
13
+ { tag: ["@21cfr"] },
14
+ async ({ page }) => {
15
+ await page.goto(process.env.ROOT_DATAVERSE ?? "/");
16
+ await page.getByRole("button", { name: "Add Data" }).click();
17
+ await page.getByRole("link", { name: "New Dataverse" }).click();
18
+ await page
19
+ .getByRole("textbox", { name: "Identifier" })
20
+ .fill("playwright-testing-collection");
21
+ await page.getByLabel("Category").selectOption("Department");
22
+ await page.getByRole("button", { name: "Create Dataverse" }).click();
23
+ await page.getByRole("button", { name: "Edit" }).click();
24
+ await page.getByRole("link", { name: "Delete Dataverse" }).click();
25
+ await page.getByRole("button", { name: "Continue" }).click();
26
+ },
27
+ );