Skip to content

Commit fb48ae2

Browse files
authored
[DAPS-1786] - Web tests, refactor: add test for hitting password reset. (#1787)
1 parent eafafee commit fb48ae2

File tree

6 files changed

+239
-82
lines changed

6 files changed

+239
-82
lines changed

tests/end-to-end/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,76 @@ To use the python API you will need to build it
1414
cmake -S. -B build -DBUILD_PYTHON_CLIENT=ON
1515
cmake --build build --target pydatafed
1616
```
17+
18+
## Playwright
19+
20+
On windows, it is recommended to run playwright directly on windows and not in
21+
a docker container or on wsl2. If you do take that approach you will likely
22+
encounter compatibility problems, and will still need to stand up an XServer
23+
on the windows host.
24+
25+
To run
26+
27+
```bash
28+
npm install .
29+
npx playwright install
30+
npx playwright test
31+
```
32+
33+
You can also use the playwright code generator to add additional tests.
34+
35+
```bash
36+
npx playwright codegen
37+
```
38+
39+
If you are running on linux you might be able to get away with running in a
40+
docker image.
41+
42+
Below is a minimal dockerfile to build playwright with a few useful developer tools.
43+
44+
```Dockerfile
45+
FROM mcr.microsoft.com/playwright:v1.45.1-noble
46+
47+
# Install Chromium only
48+
WORKDIR /work
49+
RUN npx playwright install chromium --with-deps; npx playwright install
50+
RUN apt-get update && apt-get install -y ca-certificates bash vim && update-ca-certificates
51+
```
52+
53+
Build it with.
54+
55+
```bash
56+
docker build . -t playwright:latest
57+
```
58+
59+
```bash
60+
docker run --rm -v "$PWD:/work" -w /work -e DATAFED_WEB_TEST_USERNAME="$DATAFED_WEB_TEST_USERNAME" -e DATAFED_WEB_TEST_PASSWORD="$DATAFED_WEB_TEST_PASSWORD" -e DATAFED_DOMAIN="$DATAFED_DOMAIN" -e DISPLAY=host.docker.
61+
internal:0 playwright:latest npx -y playwright test
62+
```
63+
64+
NOTE: By default the web tests are setup to run in headless mode but if you
65+
wish to see the web tests as they execute while debugging etc you will need
66+
to edit the configuration in playwright.config.js
67+
68+
This might need to be specified in the following places
69+
```
70+
projects: [
71+
{
72+
name: "chromium",
73+
use: {
74+
...devices["Desktop Chrome"],
75+
headless: false, // optional: run headed
76+
},
77+
},
78+
]
79+
```
80+
81+
```
82+
use: {
83+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
84+
trace: "on-first-retry",
85+
headless: false,
86+
screenshot: "only-on-failure",
87+
88+
```
89+

tests/end-to-end/web-UI/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ configure_file(
88
"${CMAKE_CURRENT_SOURCE_DIR}/auth.setup.js"
99
@ONLY
1010
)
11-
11+
message("ENABLE END TO END!!!!!!!")
1212
#FIXTHIS
1313
# For E2E web ui test
1414
if(ENABLE_END_TO_END_WEB_TESTS)

tests/end-to-end/web-UI/auth.setup.js.in

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,102 @@
1-
const { chromium } = require('playwright');
2-
const path = require('path');
3-
const process = require('process');
1+
const { chromium } = require("playwright");
2+
const path = require("path");
3+
const process = require("process");
44

55
console.log("INFO - ******Inside Setup file******");
66

77
module.exports = async function () {
88
// if a playwright page object doesn't exist, create one
99
const browser = await chromium.launch({
10-
args: ['--ignore-certificate-errors'],
10+
args: ["--ignore-certificate-errors"],
1111
headless: true,
1212
});
1313

14-
const page = await browser.newPage();
14+
const page = await browser.newPage();
1515
console.log("INFO - new page object created");
16-
16+
1717
// Extra safety for debugging slow loads
18-
page.on('console', msg => console.log('INFO - [PAGE LOG] ', msg.text()));
19-
page.on('response', res => {
20-
if (res.status() >= 400)
21-
console.log(`ERROR - [HTTP ${res.status()}] ${res.url()}`);
18+
page.on("console", (msg) => console.log("INFO - [PAGE LOG] ", msg.text()));
19+
page.on("response", (res) => {
20+
if (res.status() >= 400) console.log(`ERROR - [HTTP ${res.status()}] ${res.url()}`);
2221
});
2322

2423
// --- Step 1: Go to DataFed ---
2524
console.log("INFO - 1. Got to DataFed");
26-
await page.goto('https://@DATAFED_DOMAIN@/ui/welcome', { waitUntil: 'networkidle', timeout: 60000 });
25+
await page.goto("https://@DATAFED_DOMAIN@/ui/welcome", {
26+
waitUntil: "networkidle",
27+
timeout: 60000,
28+
});
2729

2830
// --- Step 2: Click Login/Register ---
2931
console.log("INFO - 2. Login and Register");
30-
const loginButton = page.getByRole('button', { name: 'Log In / Register' });
31-
await loginButton.waitFor({ state: 'visible', timeout: 30000 });
32+
const loginButton = page.getByRole("button", { name: "Log In / Register" });
33+
await loginButton.waitFor({ state: "visible", timeout: 30000 });
3234
await loginButton.click();
3335

3436
// --- Step 3: Check if link is visible ---
3537
console.log("INFO - 3. Check if link is visible");
36-
const globusLink = page.getByRole('link', { name: /Globus/i });
37-
await globusLink.waitFor({ state: 'visible', timeout: 30000 });
38+
const globusLink = page.getByRole("link", { name: /Globus/i });
39+
await globusLink.waitFor({ state: "visible", timeout: 30000 });
40+
await page.waitForTimeout(1000);
3841

3942
// --- Step 4: Click Globus ID to sign in ---
4043
console.log("INFO - 4. Use Globus ID to sign in");
41-
const globusIDButton = page.getByRole('button', { name: 'Globus ID to sign in' });
42-
await globusIDButton.waitFor({ state: 'visible', timeout: 30000 });
44+
const globusIDButton = page.getByRole("button", { name: "Globus ID to sign in" });
45+
await globusIDButton.waitFor({ state: "visible", timeout: 30000 });
4346
await globusIDButton.click();
47+
await page.waitForTimeout(1000);
4448

4549
// --- Step 5: Wait for Globus redirect ---
4650
console.log("INFO - 5. Wait for Globus login to redirect");
47-
await page.waitForLoadState('networkidle', { timeout: 45000 });
51+
await page.waitForLoadState("networkidle", { timeout: 45000 });
4852

4953
// --- Step 6: Fill in credentials robustly ---
5054
console.log("INFO - 6. Fill in credentials");
5155
const usernameField = page.getByLabel(/username/i);
52-
await usernameField.waitFor({ state: 'visible', timeout: 45000 });
56+
await usernameField.waitFor({ state: "visible", timeout: 45000 });
5357
await usernameField.fill(process.env.DATAFED_WEB_TEST_USERNAME);
5458

5559
const passwordField = page.getByLabel(/password/i);
56-
await passwordField.waitFor({ state: 'visible', timeout: 45000 });
60+
await passwordField.waitFor({ state: "visible", timeout: 45000 });
5761
await passwordField.fill(process.env.DATAFED_WEB_TEST_PASSWORD);
5862

5963
// --- Step 7: Submit form ---
6064
await page.click('button[type="submit"]');
61-
await page.waitForURL('https://@DATAFED_DOMAIN@/ui/main')
6265

66+
await Promise.race([
67+
page.waitForURL(/\/ui\/main$/, { timeout: 45000 }),
68+
page.waitForURL(/\/ui\/register$/, { timeout: 45000 }),
69+
]);
70+
71+
let current = page.url();
72+
console.log("INFO - 7a Redirected to:", current);
73+
74+
if (current.includes("/ui/register")) {
75+
console.log("INFO - 7b. Registering:", current);
76+
await page.getByRole('button', { name: 'Continue Registration' }).click();
77+
} else {
78+
console.log("INFO - 7b. Grabbing fresh url:", current);
79+
if (!page.url().includes("/ui/main")) {
80+
await page.waitForURL("https://@DATAFED_DOMAIN@/ui/main", { timeout: 45000 });
81+
}
82+
current = page.url();
83+
console.log("INFO - 7c. URL is: ", current);
84+
}
85+
86+
await page.screenshot({ path: "after_login.png", fullPage: true });
87+
if (current.includes("/ui/main")) {
88+
console.log("INFO - 8b Successful: ", current);
89+
} else {
90+
throw new Error(`Unexpected redirect URL: ${current}`);
91+
}
92+
//await Promise.all([
93+
// page.waitForNavigation({ url: /\/ui\/main/ }), // robust matching
94+
// page.click('button[type="submit"]'),
95+
//]);
96+
//page.screenshot(path="final.png", full_page=True)
6397
console.log("INFO - ******PAST LOGIN******");
64-
await page.context().storageState({ path: './.auth/auth.json'}); //TESTING
98+
await page.context().storageState({ path: "./.auth/auth.json" }); //TESTING
6599
console.log("INFO - ******Done with login******");
66100

67101
await browser.close();
68102
};
69-

tests/end-to-end/web-UI/playwright.config.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,5 @@ module.exports = defineConfig({
5050
...devices["Desktop Chrome"],
5151
},
5252
},
53-
54-
// {
55-
// name: 'firefox',
56-
// use: {
57-
// ...devices['Desktop Firefox'],
58-
// },
59-
// },
60-
61-
// {
62-
// name: 'webkit',
63-
// use: {
64-
// ...devices['Desktop Safari'],
65-
// },
66-
// },
6753
],
6854
});
Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,46 @@
11
import { test, expect } from "@playwright/test";
22

33
// checking visibility and expanding some dropdowns
4-
test("test visibility", async ({ page }) => {
5-
try {
6-
console.log("******Begin test******");
7-
// Temporary fix
8-
let domain = process.env.DATAFED_DOMAIN;
9-
await page.goto("https://" + domain + "/");
10-
if (await page.getByRole("button", { name: "Log In / Register" }).isVisible()) {
11-
console.log("NOT LOGGED IN");
4+
test.describe("DataFed UI Navigation", () => {
5+
test("should display main navigation elements", async ({ page }) => {
6+
const domain = process.env.DATAFED_DOMAIN;
7+
if (!domain) {
8+
throw new Error("DATAFED_DOMAIN environment variable not set");
129
}
13-
if (await expect(page.getByText("Continue Registration")).toBeVisible()) {
14-
await page.getByText("Continue Registration").click({ timeout: 20000 });
15-
}
16-
await expect(page.locator(".ui-icon").first()).toBeVisible({
17-
timeout: 20000,
18-
});
10+
11+
await page.goto(`https://${domain}/ui/main`);
12+
13+
// Verify main elements
14+
await expect(page.locator(".ui-icon").first()).toBeVisible();
1915
await expect(page.getByText("DataFed - Scientific Data")).toBeVisible();
2016
await expect(page.getByRole("link", { name: "My Data" })).toBeVisible();
2117
await expect(page.getByRole("link", { name: "Catalog" })).toBeVisible();
22-
await expect(page.getByRole("button", { name: "" })).toBeVisible();
18+
});
19+
20+
test("should expand tree navigation items", async ({ page }) => {
21+
const domain = process.env.DATAFED_DOMAIN;
22+
if (!domain) {
23+
throw new Error("DATAFED_DOMAIN environment variable not set");
24+
}
25+
26+
await page.goto(`https://${domain}/ui/main`);
27+
28+
// Define tree items to expand
29+
const treeItems = [
30+
"Public Collections",
31+
"Allocations",
32+
"Project Data",
33+
"Shared Data",
34+
"Saved Queries",
35+
"By User",
36+
];
2337

24-
await page
25-
.getByRole("treeitem", { name: "  Public Collections" })
26-
.getByRole("button")
27-
.click();
28-
await page
29-
.getByRole("treeitem", { name: "  Public Collections" })
30-
.getByRole("group")
31-
.click();
32-
await page.getByRole("treeitem", { name: "  Allocations" }).getByRole("button").click();
33-
await page.getByRole("treeitem", { name: "  Project Data" }).getByRole("button").click();
34-
await page.getByRole("treeitem", { name: "  Shared Data" }).getByRole("button").click();
35-
await page
36-
.getByRole("treeitem", { name: "  Saved Queries" })
37-
.locator("span")
38-
.first()
39-
.click();
40-
await page.getByRole("treeitem", { name: "  Saved Queries" }).getByRole("button").click();
41-
await page.getByText("Provenance Annotate Upload").click({ timeout: 20000 });
42-
await page.getByRole("treeitem", { name: "  By User" }).getByRole("button").click();
43-
} catch (error) {
44-
// element not visible, either the test broke due to tags changing, or not logged in
45-
// try to log out, because if not logged out, future tests will fail due to globus being annoying
46-
if (await page.getByRole("button", { name: "" }).isVisible()) {
47-
await page.getByRole("button", { name: "" }).click();
48-
} else {
49-
// if in here, check if you logged out properly
50-
throw error;
38+
for (const item of treeItems) {
39+
const treeItem = page.getByRole("treeitem", { name: new RegExp(item) });
40+
const button = treeItem.getByRole("button").first();
41+
await expect(button).toBeVisible();
42+
await button.click();
43+
// Add assertion that it expanded if needed
5144
}
52-
}
53-
//removed logout
45+
});
5446
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { test, expect } from "@playwright/test";
2+
3+
// checking visibility and expanding some dropdowns
4+
test.describe("DataFed UI password change", () => {
5+
// Since auth is handled by global setup, we start from authenticated state
6+
test.beforeEach(async ({ page }) => {
7+
const domain = process.env.DATAFED_DOMAIN || "domain.com";
8+
9+
// Navigate to main page (already authenticated via storageState)
10+
await page.goto(`https://${domain}/ui/main`, {
11+
waitUntil: "networkidle",
12+
});
13+
14+
// Verify we're on the main page and authenticated
15+
await expect(page).toHaveURL(new RegExp(`https://${domain}/ui/main`));
16+
17+
// Wait for main UI to be ready
18+
await page
19+
.waitForSelector('[data-testid="main-content"], .main-content, #main', {
20+
state: "visible",
21+
timeout: 3000,
22+
})
23+
.catch(() => {
24+
// Fallback: just wait for any main element
25+
return page.waitForTimeout(2000);
26+
});
27+
28+
// Log current page state for debugging
29+
console.log(`INFO - Test starting on: ${page.url()}`);
30+
});
31+
32+
test("should display main page after entering password", async ({ page }) => {
33+
await test.step("Open settings menu", async () => {
34+
const button = page.locator("#btn_settings");
35+
36+
if (await button.isVisible()) {
37+
console.log("INFO - Button is visible (btn_settings)");
38+
await button.click();
39+
} else {
40+
console.log("INFO - Button is not visible (btn_settings)");
41+
}
42+
});
43+
44+
await test.step("Enter password and confirmation password", async () => {
45+
// Define locators for the elements
46+
const newPasswordInput = page.locator("#cli_new_pw");
47+
const confirmPasswordInput = page.locator("#cli_confirm_pw");
48+
const revokeButton = page.locator("#btn_revoke_cred");
49+
const saveButton = page.getByRole("button", { name: "Save" });
50+
51+
// Wait for all elements to be visible
52+
await Promise.all([
53+
newPasswordInput.waitFor({ state: "visible", timeout: 5000 }),
54+
confirmPasswordInput.waitFor({ state: "visible", timeout: 5000 }),
55+
revokeButton.waitFor({ state: "visible", timeout: 5000 }),
56+
saveButton.waitFor({ state: "visible", timeout: 5000 }),
57+
]);
58+
await newPasswordInput.click();
59+
await newPasswordInput.fill("Terrible2s!!!");
60+
await confirmPasswordInput.click();
61+
await confirmPasswordInput.fill("Terrible2s!!!");
62+
await saveButton.click();
63+
// Make sure an error does not appear.
64+
// Unfortunately it take a while for the error to show up
65+
await page.waitForTimeout(15000);
66+
67+
await page.screenshot({ path: "change-password-has-error.png", fullPage: true });
68+
69+
// These WILL fail if error appears
70+
await expect(page.getByText("Save Settings Error")).not.toBeVisible();
71+
});
72+
});
73+
});

0 commit comments

Comments
 (0)