@umbraco/playwright-testhelpers 17.0.7 → 17.0.9

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.
@@ -6,12 +6,12 @@ export declare class LoginApiHelper {
6
6
  constructor(api: ApiHelpers, page: Page);
7
7
  login(userEmail: string, password: string): Promise<{
8
8
  cookie: string;
9
- accessToken: any;
10
- refreshToken: any;
9
+ setCookies: string;
11
10
  }>;
11
+ extractPKCECodeFromSetCookie(setCookies: string): Promise<string>;
12
12
  getCookie(userEmail: string, password: string): Promise<string>;
13
13
  createCodeChallenge(codeVerifier: string): Promise<string>;
14
- getAuthorizationCode(codeChallenge: string, cookie: string, stateValue: string): Promise<string | null>;
15
- getRefreshToken(cookie: string, codeVerifier: string, authorizationCode: any): Promise<any>;
14
+ getAuthorizationSetCookie(codeChallenge: string, cookie: string, stateValue: string): Promise<string>;
15
+ getCookiesWithAccessTokenAndRefreshToken(cookie: string, codeVerifier: string, PKCECookie: string): Promise<string>;
16
16
  getAccessToken(cookie: string, refreshToken: string): Promise<any>;
17
17
  }
@@ -14,10 +14,14 @@ class LoginApiHelper {
14
14
  const stateValue = 'myStateValue'; // A static state value for testing
15
15
  const cookie = await this.getCookie(userEmail, password);
16
16
  const codeChallenge = await this.createCodeChallenge(codeVerifier);
17
- const authorizationCode = await this.getAuthorizationCode(codeChallenge, cookie, stateValue);
18
- const refreshToken = await this.getRefreshToken(cookie, codeVerifier, authorizationCode);
19
- const accessToken = await this.getAccessToken(cookie, refreshToken.refresh_token);
20
- return { cookie, accessToken, refreshToken };
17
+ const authorizationSetCookie = await this.getAuthorizationSetCookie(codeChallenge, cookie, stateValue);
18
+ const PKCECookie = await this.extractPKCECodeFromSetCookie(authorizationSetCookie);
19
+ const setCookies = await this.getCookiesWithAccessTokenAndRefreshToken(cookie, codeVerifier, PKCECookie);
20
+ return { cookie, setCookies };
21
+ }
22
+ async extractPKCECodeFromSetCookie(setCookies) {
23
+ const match = setCookies.match(/.*(__Host-umbPkceCode=[A-Za-z0-9_-]+;)/s);
24
+ return match?.[1] ?? "";
21
25
  }
22
26
  async getCookie(userEmail, password) {
23
27
  const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/login', {
@@ -38,7 +42,7 @@ class LoginApiHelper {
38
42
  async createCodeChallenge(codeVerifier) {
39
43
  return (0, crypto_1.createHash)('sha256').update(codeVerifier, 'utf8').digest('base64').replace(/=/g, '').trim();
40
44
  }
41
- async getAuthorizationCode(codeChallenge, cookie, stateValue) {
45
+ async getAuthorizationSetCookie(codeChallenge, cookie, stateValue) {
42
46
  const authorizationUrl = `${this.api.baseUrl}/umbraco/management/api/v1/security/back-office/authorize?client_id=umbraco-back-office&response_type=code&redirect_uri=${encodeURIComponent(this.api.baseUrl + '/umbraco/oauth_complete')}&code_challenge_method=S256&code_challenge=${codeChallenge}&state=${stateValue}&scope=offline_access&prompt=consent&access_type=offline`;
43
47
  const response = await this.page.request.get(authorizationUrl, {
44
48
  headers: {
@@ -48,34 +52,31 @@ class LoginApiHelper {
48
52
  ignoreHTTPSErrors: true,
49
53
  maxRedirects: 0
50
54
  });
51
- // Parse the authorization code from the redirect URL
52
- const locationHeader = response.headers()['location'];
53
- if (!locationHeader) {
54
- throw new Error('Authorization redirect location not found');
55
+ if (response.status() !== 200) {
56
+ console.error('Failed to retrieve cookie');
55
57
  }
56
- // Extract the authorization code from the location header
57
- return new URLSearchParams(locationHeader.split('?')[1]).get('code');
58
+ return response.headers()['set-cookie'];
58
59
  }
59
- async getRefreshToken(cookie, codeVerifier, authorizationCode) {
60
+ async getCookiesWithAccessTokenAndRefreshToken(cookie, codeVerifier, PKCECookie) {
60
61
  const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {
61
62
  headers: {
62
63
  'Content-Type': 'application/x-www-form-urlencoded',
63
- Cookie: cookie,
64
+ Cookie: PKCECookie + cookie,
64
65
  Origin: this.api.baseUrl
65
66
  },
66
67
  form: {
67
68
  grant_type: 'authorization_code',
68
69
  client_id: 'umbraco-back-office',
69
70
  redirect_uri: this.api.baseUrl + '/umbraco/oauth_complete',
70
- code: authorizationCode,
71
+ code: '[redacted]',
71
72
  code_verifier: codeVerifier
72
73
  },
73
74
  ignoreHTTPSErrors: true
74
75
  });
75
76
  if (response.status() !== 200) {
76
- console.error('Failed to retrieve refresh token');
77
+ console.error('Failed to retrieve cookie');
77
78
  }
78
- return await response.json();
79
+ return response.headers()['set-cookie'];
79
80
  }
80
81
  async getAccessToken(cookie, refreshToken) {
81
82
  const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {
@@ -1 +1 @@
1
- {"version":3,"file":"LoginApiHelper.js","sourceRoot":"","sources":["../../../lib/helpers/LoginApiHelper.ts"],"names":[],"mappings":"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","sourcesContent":["import {ApiHelpers} from \"./ApiHelpers\";\nimport {Page} from \"@playwright/test\";\nimport {createHash} from \"crypto\";\n\nexport class LoginApiHelper {\n api: ApiHelpers;\n page: Page;\n\n constructor(api: ApiHelpers, page: Page) {\n this.api = api;\n this.page = page;\n }\n\n public async login(userEmail: string, password: string) {\n const codeVerifier = \"12345\"; // A static state value for testing\n const stateValue = 'myStateValue'; // A static state value for testing\n const cookie = await this.getCookie(userEmail, password);\n const codeChallenge = await this.createCodeChallenge(codeVerifier);\n const authorizationCode = await this.getAuthorizationCode(codeChallenge, cookie, stateValue);\n const refreshToken = await this.getRefreshToken(cookie, codeVerifier, authorizationCode);\n const accessToken = await this.getAccessToken(cookie, refreshToken.refresh_token);\n return {cookie, accessToken, refreshToken};\n }\n\n async getCookie(userEmail: string, password: string) {\n const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/login', {\n headers: {\n 'Content-Type': 'application/json',\n Referer: this.api.baseUrl,\n Origin: this.api.baseUrl,\n },\n data: {\n username: userEmail,\n password: password\n },\n ignoreHTTPSErrors: true\n });\n\n // Ensure the cookie is properly captured\n return response.headers()['set-cookie'];\n }\n\n async createCodeChallenge(codeVerifier: string) {\n return createHash('sha256').update(codeVerifier, 'utf8').digest('base64').replace(/=/g, '').trim();\n }\n\n async getAuthorizationCode(codeChallenge: string, cookie: string, stateValue: string) {\n const authorizationUrl = `${this.api.baseUrl}/umbraco/management/api/v1/security/back-office/authorize?client_id=umbraco-back-office&response_type=code&redirect_uri=${encodeURIComponent(this.api.baseUrl + '/umbraco/oauth_complete')}&code_challenge_method=S256&code_challenge=${codeChallenge}&state=${stateValue}&scope=offline_access&prompt=consent&access_type=offline`;\n const response = await this.page.request.get(authorizationUrl, {\n headers: {\n Cookie: cookie,\n Referer: this.api.baseUrl,\n },\n ignoreHTTPSErrors: true,\n maxRedirects: 0\n });\n\n // Parse the authorization code from the redirect URL\n const locationHeader = response.headers()['location'];\n if (!locationHeader) {\n throw new Error('Authorization redirect location not found');\n }\n // Extract the authorization code from the location header\n return new URLSearchParams(locationHeader.split('?')[1]).get('code');\n }\n\n async getRefreshToken(cookie: string, codeVerifier: string, authorizationCode) {\n const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Cookie: cookie,\n Origin: this.api.baseUrl\n },\n form: {\n grant_type: 'authorization_code',\n client_id: 'umbraco-back-office',\n redirect_uri: this.api.baseUrl + '/umbraco/oauth_complete',\n code: authorizationCode,\n code_verifier: codeVerifier\n },\n ignoreHTTPSErrors: true\n });\n\n if (response.status() !== 200) {\n console.error('Failed to retrieve refresh token');\n }\n return await response.json();\n }\n\n async getAccessToken(cookie: string, refreshToken: string) {\n const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Cookie: cookie,\n Origin: this.api.baseUrl\n },\n form: {\n grant_type: 'refresh_token',\n client_id: 'umbraco-back-office',\n redirect_uri: this.api.baseUrl + '/umbraco/oauth_complete',\n refresh_token: refreshToken,\n },\n ignoreHTTPSErrors: true\n });\n\n if (response.status() === 200) {\n console.log('Login successful');\n } else {\n console.error('Login failed');\n }\n return await response.json();\n }\n}\n"]}
1
+ {"version":3,"file":"LoginApiHelper.js","sourceRoot":"","sources":["../../../lib/helpers/LoginApiHelper.ts"],"names":[],"mappings":"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","sourcesContent":["import {ApiHelpers} from \"./ApiHelpers\";\nimport {Page} from \"@playwright/test\";\nimport {createHash} from \"crypto\";\n\nexport class LoginApiHelper {\n api: ApiHelpers;\n page: Page;\n\n constructor(api: ApiHelpers, page: Page) {\n this.api = api;\n this.page = page;\n }\n\n public async login(userEmail: string, password: string) {\n const codeVerifier = \"12345\"; // A static state value for testing\n const stateValue = 'myStateValue'; // A static state value for testing\n const cookie = await this.getCookie(userEmail, password);\n const codeChallenge = await this.createCodeChallenge(codeVerifier);\n const authorizationSetCookie = await this.getAuthorizationSetCookie(codeChallenge, cookie, stateValue);\n const PKCECookie = await this.extractPKCECodeFromSetCookie(authorizationSetCookie);\n const setCookies = await this.getCookiesWithAccessTokenAndRefreshToken(cookie, codeVerifier, PKCECookie);\n return {cookie, setCookies};\n }\n\n async extractPKCECodeFromSetCookie(setCookies: string) {\n const match = setCookies.match(/.*(__Host-umbPkceCode=[A-Za-z0-9_-]+;)/s);\n return match?.[1] ?? \"\";\n }\n\n async getCookie(userEmail: string, password: string) {\n const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/login', {\n headers: {\n 'Content-Type': 'application/json',\n Referer: this.api.baseUrl,\n Origin: this.api.baseUrl,\n },\n data: {\n username: userEmail,\n password: password\n },\n ignoreHTTPSErrors: true\n });\n\n // Ensure the cookie is properly captured\n return response.headers()['set-cookie'];\n }\n\n async createCodeChallenge(codeVerifier: string) {\n return createHash('sha256').update(codeVerifier, 'utf8').digest('base64').replace(/=/g, '').trim();\n }\n\n async getAuthorizationSetCookie(codeChallenge: string, cookie: string, stateValue: string) {\n const authorizationUrl = `${this.api.baseUrl}/umbraco/management/api/v1/security/back-office/authorize?client_id=umbraco-back-office&response_type=code&redirect_uri=${encodeURIComponent(this.api.baseUrl + '/umbraco/oauth_complete')}&code_challenge_method=S256&code_challenge=${codeChallenge}&state=${stateValue}&scope=offline_access&prompt=consent&access_type=offline`;\n const response = await this.page.request.get(authorizationUrl, {\n headers: {\n Cookie: cookie,\n Referer: this.api.baseUrl,\n },\n ignoreHTTPSErrors: true,\n maxRedirects: 0\n });\n\n if (response.status() !== 200) {\n console.error('Failed to retrieve cookie');\n }\n return response.headers()['set-cookie'];\n }\n\n async getCookiesWithAccessTokenAndRefreshToken(cookie: string, codeVerifier: string, PKCECookie: string) {\n const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Cookie: PKCECookie + cookie,\n Origin: this.api.baseUrl\n },\n form: {\n grant_type: 'authorization_code',\n client_id: 'umbraco-back-office',\n redirect_uri: this.api.baseUrl + '/umbraco/oauth_complete',\n code: '[redacted]',\n code_verifier: codeVerifier\n },\n ignoreHTTPSErrors: true\n });\n\n if (response.status() !== 200) {\n console.error('Failed to retrieve cookie');\n }\n return response.headers()['set-cookie'];\n }\n\n async getAccessToken(cookie: string, refreshToken: string) {\n const response = await this.page.request.post(this.api.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Cookie: cookie,\n Origin: this.api.baseUrl\n },\n form: {\n grant_type: 'refresh_token',\n client_id: 'umbraco-back-office',\n redirect_uri: this.api.baseUrl + '/umbraco/oauth_complete',\n refresh_token: refreshToken,\n },\n ignoreHTTPSErrors: true\n });\n\n if (response.status() === 200) {\n console.log('Login successful');\n } else {\n console.error('Login failed');\n }\n return await response.json();\n }\n}\n"]}
@@ -152,6 +152,7 @@ export declare class UiBaseLocators {
152
152
  readonly uiLoader: Locator;
153
153
  readonly createDocumentBlueprintModal: Locator;
154
154
  readonly entityItem: Locator;
155
+ readonly sectionLinks: Locator;
155
156
  constructor(page: Page);
156
157
  clickActionsMenuForNameInSectionSidebar(name: string): Promise<void>;
157
158
  clickActionsMenuForName(name: string): Promise<void>;
@@ -156,6 +156,7 @@ class UiBaseLocators {
156
156
  uiLoader;
157
157
  createDocumentBlueprintModal;
158
158
  entityItem;
159
+ sectionLinks;
159
160
  constructor(page) {
160
161
  this.page = page;
161
162
  this.saveBtn = page.getByLabel('Save', { exact: true });
@@ -303,6 +304,7 @@ class UiBaseLocators {
303
304
  this.successStateIcon = this.successState.locator('#state');
304
305
  this.workspaceAction = page.locator('umb-workspace-action');
305
306
  this.caretBtn = page.locator('#caret-button');
307
+ this.sectionLinks = page.getByTestId('section-links');
306
308
  // Entity Action
307
309
  this.entityAction = page.locator('umb-entity-action-list umb-entity-action');
308
310
  this.openEntityAction = page.locator('#action-modal[open]').locator(this.entityAction);
@@ -319,6 +321,8 @@ class UiBaseLocators {
319
321
  }
320
322
  async clickActionsMenuForName(name) {
321
323
  await (0, test_1.expect)(this.page.locator('uui-menu-item[label="' + name + '"]').locator('#menu-item').first()).toBeVisible();
324
+ // We need to wait for the load to be finished, otherwise we would run into flaky tests
325
+ await this.page.waitForTimeout(1000);
322
326
  await this.page.locator('uui-menu-item[label="' + name + '"]').locator('#menu-item').first().hover({ force: true });
323
327
  await this.page.locator('uui-menu-item[label="' + name + '"] #action-modal').first().click({ force: true });
324
328
  }
@@ -596,10 +600,17 @@ class UiBaseLocators {
596
600
  async goToSection(sectionName, checkSections = true) {
597
601
  if (checkSections) {
598
602
  for (let section in ConstantHelper_1.ConstantHelper.sections) {
599
- await (0, test_1.expect)(this.backOfficeHeader.getByRole('tab', { name: ConstantHelper_1.ConstantHelper.sections[section] })).toBeVisible({ timeout: 30000 });
603
+ await (0, test_1.expect)(this.sectionLinks.getByRole('tab', { name: ConstantHelper_1.ConstantHelper.sections[section] })).toBeVisible({ timeout: 30000 });
600
604
  }
601
605
  }
602
- await this.backOfficeHeader.getByRole('tab', { name: sectionName }).click();
606
+ // We need to check if we are on the section tab already, if we are, then we need to reload the page instead of clicking again
607
+ const alreadySelected = await this.sectionLinks.locator('[active]').getByText(sectionName).isVisible();
608
+ if (alreadySelected) {
609
+ await this.page.reload();
610
+ }
611
+ else {
612
+ await this.backOfficeHeader.getByRole('tab', { name: sectionName }).click();
613
+ }
603
614
  }
604
615
  async goToSettingsTreeItem(settingsTreeItemName) {
605
616
  await this.goToSection(ConstantHelper_1.ConstantHelper.sections.settings);