Skip to content

Commit 2b90ecd

Browse files
committed
Refactor navigation URL resolution for Keycloak, Nextcloud, and OpenProject pages
- Introduced `resolveNavigationUrl` method in base page classes to standardize URL resolution. - Updated Keycloak and Nextcloud page classes to utilize the new method. - Enhanced OpenProject page classes to resolve URLs based on environment variables and configuration. - Added utility functions `resolveServiceNavigationUrl` and `resolveServiceOrigin` for improved URL handling in `url-helpers.ts`. - Updated environment variable handling in the E2E PullPreview workflow to include new URLs.
1 parent 8e25177 commit 2b90ecd

8 files changed

Lines changed: 135 additions & 8 deletions

File tree

.github/workflows/e2e-pullpreview.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ jobs:
330330
echo "OPENPROJECT_HOST=${preview_host}"
331331
echo "NEXTCLOUD_HOST=nextcloud.${preview_host}"
332332
echo "KEYCLOAK_HOST=keycloak.${preview_host}"
333+
echo "OPENPROJECT_URL=${preview_url}"
334+
echo "NEXTCLOUD_URL=${nextcloud_url}"
333335
echo "E2E_ENV=pullpreview"
334336
} >> "$GITHUB_ENV"
335337

pageobjects/base/BasePage.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ export abstract class BasePage {
4848
* Navigate to the page URL
4949
*/
5050
async navigateTo(): Promise<void> {
51-
const url = process.env[this.getUrlEnvVar()] || this.locators.url;
51+
const url = this.resolveNavigationUrl();
5252
logDebug('[PAGE NAVIGATION] Navigating to:', url);
5353
await this.page.goto(url, { waitUntil: 'domcontentloaded' });
5454
logDebug('[PAGE NAVIGATION] Current URL:', this.page.url());
5555
logDebug('[PAGE NAVIGATION] Page title:', await this.page.title());
5656
}
5757

58+
/**
59+
* Resolved start URL for this page. Domain base pages override to honor *_HOST and testConfig.
60+
*/
61+
protected resolveNavigationUrl(): string {
62+
const key = this.getUrlEnvVar();
63+
return (key && process.env[key]) || this.locators.url;
64+
}
65+
5866
/**
5967
* Get the environment variable name for the URL
6068
* Override in subclasses if needed

pageobjects/keycloak/KeycloakBasePage.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Page } from '@playwright/test';
22
import { BasePage } from '../base/BasePage';
3+
import { testConfig } from '../../utils/config';
4+
import { resolveServiceNavigationUrl } from '../../utils/url-helpers';
35

46
export abstract class KeycloakBasePage extends BasePage {
57
constructor(page: Page) {
@@ -10,8 +12,22 @@ export abstract class KeycloakBasePage extends BasePage {
1012
return 'KEYCLOAK_URL';
1113
}
1214

15+
protected resolveNavigationUrl(): string {
16+
return resolveServiceNavigationUrl(
17+
process.env.KEYCLOAK_URL,
18+
process.env.KEYCLOAK_HOST,
19+
testConfig.keycloak.host,
20+
this.locators.url,
21+
);
22+
}
23+
1324
protected get baseUrl(): string {
14-
return process.env.KEYCLOAK_URL || this.locators.url;
25+
return resolveServiceNavigationUrl(
26+
process.env.KEYCLOAK_URL,
27+
process.env.KEYCLOAK_HOST,
28+
testConfig.keycloak.host,
29+
this.locators.url,
30+
);
1531
}
1632
}
1733

pageobjects/keycloak/KeycloakLoginPage.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { ADMIN_USER } from '../../utils/test-users';
44
import { testConfig } from '../../utils/config';
55
import { escapeForRegex, resolveHostname } from '../../utils/url-helpers';
66

7-
const keycloakHost = resolveHostname(process.env.KEYCLOAK_URL) || resolveHostname(testConfig.keycloak.host) || 'keycloak.test';
7+
const keycloakHost =
8+
resolveHostname(process.env.KEYCLOAK_URL) ||
9+
resolveHostname(process.env.KEYCLOAK_HOST) ||
10+
resolveHostname(testConfig.keycloak.host) ||
11+
'keycloak.test';
812
const keycloakHostPattern = new RegExp(`.*${escapeForRegex(keycloakHost)}.*`);
913

1014
export class KeycloakLoginPage extends KeycloakBasePage {

pageobjects/nextcloud/NextcloudBasePage.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Page } from '@playwright/test';
22
import { BasePage } from '../base/BasePage';
3+
import { testConfig } from '../../utils/config';
4+
import { resolveServiceNavigationUrl } from '../../utils/url-helpers';
35

46
export abstract class NextcloudBasePage extends BasePage {
57
constructor(page: Page) {
@@ -10,8 +12,22 @@ export abstract class NextcloudBasePage extends BasePage {
1012
return 'NEXTCLOUD_URL';
1113
}
1214

15+
protected resolveNavigationUrl(): string {
16+
return resolveServiceNavigationUrl(
17+
process.env.NEXTCLOUD_URL,
18+
process.env.NEXTCLOUD_HOST,
19+
testConfig.nextcloud.host,
20+
this.locators.url,
21+
);
22+
}
23+
1324
protected get baseUrl(): string {
14-
return process.env.NEXTCLOUD_URL || this.locators.url;
25+
return resolveServiceNavigationUrl(
26+
process.env.NEXTCLOUD_URL,
27+
process.env.NEXTCLOUD_HOST,
28+
testConfig.nextcloud.host,
29+
this.locators.url,
30+
);
1531
}
1632
}
1733

pageobjects/openproject/OpenProjectBasePage.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { Page } from '@playwright/test';
22
import { BasePage } from '../base/BasePage';
33
import { testConfig } from '../../utils/config';
4-
import { escapeForRegex, resolveHostname } from '../../utils/url-helpers';
4+
import { escapeForRegex, resolveHostname, resolveServiceNavigationUrl, resolveServiceOrigin } from '../../utils/url-helpers';
55

6-
const openProjectHost = resolveHostname(process.env.OPENPROJECT_URL) || resolveHostname(testConfig.openproject.host) || 'openproject.test';
6+
const openProjectHost =
7+
resolveHostname(process.env.OPENPROJECT_URL) ||
8+
resolveHostname(process.env.OPENPROJECT_HOST) ||
9+
resolveHostname(testConfig.openproject.host) ||
10+
'openproject.test';
711
const openProjectHostPattern = new RegExp(`.*${escapeForRegex(openProjectHost)}.*`);
812

913
export abstract class OpenProjectBasePage extends BasePage {
@@ -15,8 +19,22 @@ export abstract class OpenProjectBasePage extends BasePage {
1519
return 'OPENPROJECT_URL';
1620
}
1721

22+
protected resolveNavigationUrl(): string {
23+
return resolveServiceNavigationUrl(
24+
process.env.OPENPROJECT_URL,
25+
process.env.OPENPROJECT_HOST,
26+
testConfig.openproject.host,
27+
this.locators.url,
28+
);
29+
}
30+
1831
protected get baseUrl(): string {
19-
return process.env.OPENPROJECT_URL || this.locators.url.replace('/login', '');
32+
return resolveServiceOrigin(
33+
process.env.OPENPROJECT_URL,
34+
process.env.OPENPROJECT_HOST,
35+
testConfig.openproject.host,
36+
this.locators.url,
37+
);
2038
}
2139

2240
// URL path constants

pageobjects/openproject/OpenProjectLoginPage.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import { OP_ADMIN_USER } from '../../utils/test-users';
66
import { testConfig } from '../../utils/config';
77
import { escapeForRegex, resolveHostname } from '../../utils/url-helpers';
88

9-
const keycloakHost = resolveHostname(process.env.KEYCLOAK_URL) || resolveHostname(testConfig.keycloak.host) || 'keycloak.test';
9+
const keycloakHost =
10+
resolveHostname(process.env.KEYCLOAK_URL) ||
11+
resolveHostname(process.env.KEYCLOAK_HOST) ||
12+
resolveHostname(testConfig.keycloak.host) ||
13+
'keycloak.test';
1014
const keycloakHostPattern = new RegExp(`.*${escapeForRegex(keycloakHost)}.*`);
1115

1216
export class OpenProjectLoginPage extends OpenProjectBasePage {

utils/url-helpers.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,62 @@ export function resolveHostname(value?: string): string {
1616
return value;
1717
}
1818
}
19+
20+
/**
21+
* Resolve the URL used for `page.goto` for a service: prefer full URL env, then build from
22+
* host env / config host using pathname + search from the locator default, else locator default.
23+
*/
24+
export function resolveServiceNavigationUrl(
25+
fullUrlEnv: string | undefined,
26+
hostEnv: string | undefined,
27+
configHost: string,
28+
locatorDefaultFullUrl: string,
29+
): string {
30+
const full = fullUrlEnv?.trim();
31+
if (full) {
32+
try {
33+
const u = new URL(full);
34+
const loc = new URL(locatorDefaultFullUrl);
35+
if ((u.pathname === '/' || u.pathname === '') && loc.pathname && loc.pathname !== '/') {
36+
return `${u.origin}${loc.pathname}${loc.search}`;
37+
}
38+
return full;
39+
} catch {
40+
return full;
41+
}
42+
}
43+
44+
const hostLike = (hostEnv || configHost || '').trim();
45+
if (!hostLike) return locatorDefaultFullUrl;
46+
47+
let origin: string;
48+
try {
49+
const withProto = hostLike.includes('://') ? hostLike : `https://${hostLike}`;
50+
const u = new URL(withProto);
51+
origin = `${u.protocol}//${u.host}`;
52+
} catch {
53+
return locatorDefaultFullUrl;
54+
}
55+
56+
try {
57+
const loc = new URL(locatorDefaultFullUrl);
58+
return `${origin}${loc.pathname}${loc.search}`;
59+
} catch {
60+
return `${origin}/`;
61+
}
62+
}
63+
64+
/** Site origin for building path-based URLs (e.g. OpenProject base without `/login`). */
65+
export function resolveServiceOrigin(
66+
fullUrlEnv: string | undefined,
67+
hostEnv: string | undefined,
68+
configHost: string,
69+
locatorDefaultFullUrl: string,
70+
): string {
71+
const nav = resolveServiceNavigationUrl(fullUrlEnv, hostEnv, configHost, locatorDefaultFullUrl);
72+
try {
73+
return new URL(nav).origin;
74+
} catch {
75+
return nav;
76+
}
77+
}

0 commit comments

Comments
 (0)