@umbraco/playwright-testhelpers
Version:
Test helpers for making playwright tests for Umbraco solutions
457 lines • 20.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiHelpers = void 0;
const umbraco_config_1 = require("../../umbraco.config");
const ReportHelper_1 = require("./ReportHelper");
const TelemetryDataApiHelper_1 = require("./TelemetryDataApiHelper");
const LanguageApiHelper_1 = require("./LanguageApiHelper");
const DictionaryApiHelper_1 = require("./DictionaryApiHelper");
const RelationTypeApiHelper_1 = require("./RelationTypeApiHelper");
const UserGroupApiHelper_1 = require("./UserGroupApiHelper");
const TemplateApiHelper_1 = require("./TemplateApiHelper");
const AliasHelper_1 = require("./AliasHelper");
const DataTypeApiHelper_1 = require("./DataTypeApiHelper");
const UserApiHelper_1 = require("./UserApiHelper");
const TemporaryFileApiHelper_1 = require("./TemporaryFileApiHelper");
const PackageApiHelper_1 = require("./PackageApiHelper");
const ScriptApiHelper_1 = require("./ScriptApiHelper");
const PartialViewApiHelper_1 = require("./PartialViewApiHelper");
const StylesheetApiHelper_1 = require("./StylesheetApiHelper");
const fs = require("fs");
const LogViewerApiHelper_1 = require("./LogViewerApiHelper");
const DocumentTypeApiHelper_1 = require("./DocumentTypeApiHelper");
const DocumentApiHelper_1 = require("./DocumentApiHelper");
const MediaTypeApiHelper_1 = require("./MediaTypeApiHelper");
const MediaApiHelper_1 = require("./MediaApiHelper");
const ObjectTypesApiHelper_1 = require("./ObjectTypesApiHelper");
const ModelsBuilderApiHelper_1 = require("./ModelsBuilderApiHelper");
const HealthCheckApiHelper_1 = require("./HealthCheckApiHelper");
const IndexerApiHelper_1 = require("./IndexerApiHelper");
const PublishedCacheApiHelper_1 = require("./PublishedCacheApiHelper");
const RedirectManagementApiHelper_1 = require("./RedirectManagementApiHelper");
const MemberGroupApiHelper_1 = require("./MemberGroupApiHelper");
const MemberApiHelper_1 = require("./MemberApiHelper");
const MemberTypeApiHelper_1 = require("./MemberTypeApiHelper");
const DocumentBlueprintApiHelper_1 = require("./DocumentBlueprintApiHelper");
const LoginApiHelper_1 = require("./LoginApiHelper");
const WebhookApiHelper_1 = require("./WebhookApiHelper");
const MediaDeliveryApiHelper_1 = require("./differentAppSettingsHelpers/MediaDeliveryApiHelper");
const ContentDeliveryApiHelper_1 = require("./differentAppSettingsHelpers/ContentDeliveryApiHelper");
const SmtpApiHelper_1 = require("./SmtpApiHelper");
const ElementApiHelper_1 = require("./ElementApiHelper");
class ApiHelpers {
baseUrl = umbraco_config_1.umbracoConfig.environment.baseUrl;
page;
alias;
report;
telemetry;
language;
dictionary;
relationType;
userGroup;
template;
dataType;
user;
temporaryFile;
documentType;
document;
package;
script;
partialView;
stylesheet;
logViewer;
mediaType;
media;
objectTypes;
modelsBuilder;
healthCheck;
indexer;
publishedCache;
redirectManagement;
memberGroup;
member;
memberType;
documentBlueprint;
login;
webhook;
mediaDeliveryApi;
contentDeliveryApi;
smtp;
element;
constructor(page) {
this.page = page;
this.alias = new AliasHelper_1.AliasHelper();
this.report = new ReportHelper_1.ReportHelper(this);
this.telemetry = new TelemetryDataApiHelper_1.TelemetryDataApiHelper(this);
this.language = new LanguageApiHelper_1.LanguageApiHelper(this);
this.dictionary = new DictionaryApiHelper_1.DictionaryApiHelper(this);
this.relationType = new RelationTypeApiHelper_1.RelationTypeApiHelper(this);
this.userGroup = new UserGroupApiHelper_1.UserGroupApiHelper(this);
this.template = new TemplateApiHelper_1.TemplateApiHelper(this);
this.dataType = new DataTypeApiHelper_1.DataTypeApiHelper(this);
this.user = new UserApiHelper_1.UserApiHelper(this, page);
this.temporaryFile = new TemporaryFileApiHelper_1.TemporaryFileApiHelper(this);
this.documentType = new DocumentTypeApiHelper_1.DocumentTypeApiHelper(this);
this.document = new DocumentApiHelper_1.DocumentApiHelper(this);
this.package = new PackageApiHelper_1.PackageApiHelper(this);
this.script = new ScriptApiHelper_1.ScriptApiHelper(this);
this.partialView = new PartialViewApiHelper_1.PartialViewApiHelper(this);
this.stylesheet = new StylesheetApiHelper_1.StylesheetApiHelper(this);
this.logViewer = new LogViewerApiHelper_1.LogViewerApiHelper(this);
this.mediaType = new MediaTypeApiHelper_1.MediaTypeApiHelper(this);
this.media = new MediaApiHelper_1.MediaApiHelper(this);
this.objectTypes = new ObjectTypesApiHelper_1.ObjectTypesApiHelper(this);
this.modelsBuilder = new ModelsBuilderApiHelper_1.ModelsBuilderApiHelper(this);
this.healthCheck = new HealthCheckApiHelper_1.HealthCheckApiHelper(this);
this.indexer = new IndexerApiHelper_1.IndexerApiHelper(this);
this.publishedCache = new PublishedCacheApiHelper_1.PublishedCacheApiHelper(this);
this.redirectManagement = new RedirectManagementApiHelper_1.RedirectManagementApiHelper(this);
this.memberGroup = new MemberGroupApiHelper_1.MemberGroupApiHelper(this);
this.member = new MemberApiHelper_1.MemberApiHelper(this);
this.memberType = new MemberTypeApiHelper_1.MemberTypeApiHelper(this);
this.documentBlueprint = new DocumentBlueprintApiHelper_1.DocumentBlueprintApiHelper(this);
this.login = new LoginApiHelper_1.LoginApiHelper(this, this.page);
this.webhook = new WebhookApiHelper_1.WebhookApiHelper(this, this.page);
this.mediaDeliveryApi = new MediaDeliveryApiHelper_1.MediaDeliveryApiHelper(this);
this.contentDeliveryApi = new ContentDeliveryApiHelper_1.ContentDeliveryApiHelper(this);
this.smtp = new SmtpApiHelper_1.SmtpApiHelper(this);
this.element = new ElementApiHelper_1.ElementApiHelper(this);
}
async getAccessToken() {
const authToken = await this.getLocalStorageAuthToken();
return authToken.access_token;
}
async getBearerToken() {
return 'Bearer ' + await this.getAccessToken();
}
async getCookie() {
let someStorage = await this.page.context().storageState();
let cookieString = "";
for (let cookie of someStorage.cookies) {
cookieString += cookie.name + '=' + cookie.value + ';';
}
return cookieString;
}
async getHeaders() {
return {
'Authorization': await this.readLocalBearerToken(),
'Cookie': await this.readLocalCookie(),
};
}
async get(url, params, extraHeaders) {
const headers = await this.getHeaders();
const allHeaders = { ...headers, ...extraHeaders };
const options = {
headers: allHeaders,
params: params,
ignoreHTTPSErrors: true
};
return await this.page.request.get(url, options);
}
async saveCodeFile(codeFile) {
if (codeFile == null) {
return;
}
return await this.post(umbraco_config_1.umbracoConfig.environment.baseUrl + '/umbraco/backoffice/UmbracoApi/CodeFile/PostSave', codeFile);
}
async post(url, data) {
const options = {
headers: await this.getHeaders(),
data: data,
ignoreHTTPSErrors: true
};
return await this.page.request.post(url, options);
}
async delete(url, data) {
const options = {
headers: await this.getHeaders(),
data: data,
ignoreHTTPSErrors: true
};
return await this.page.request.delete(url, options);
}
async put(url, data) {
const options = {
headers: await this.getHeaders(),
data: data,
ignoreHTTPSErrors: true
};
return await this.page.request.put(url, options);
}
async postMultiPartForm(url, id, name, mimeType, filePath) {
const options = {
headers: await this.getHeaders(),
multipart: {
Id: id,
File: {
name: name,
mimeType: mimeType,
buffer: fs.readFileSync(filePath)
}
},
ignoreHTTPSErrors: true
};
return await this.page.request.post(url, options);
}
async getTokenIssuedTime() {
const authToken = await this.getLocalStorageAuthToken();
return Number(authToken.issued_at);
}
async getTokenExpireTime() {
const authToken = await this.getLocalStorageAuthToken();
return Number(authToken.expires_in);
}
async isLoginStateValid() {
// We need to get the time from the actual local storage otherwise the time would be increased for each test, and we would never refresh the login state.
const tokenTimeIssued = await this.getLocalIssuedAtTokenTime();
const tokenExpireTime = await this.getTokenExpireTime();
// Should use a global value
const globalTestTimeout = 45;
// We want to have the date minus the globalTimeout, the reason for this is that while a test is running, the token could expire.
// The refresh token lasts for 300 seconds, while the access token lasts for 60 seconds (NOT TOTALLY SURE) this is why we add 240 seconds
const tokenRefreshTime = tokenTimeIssued + tokenExpireTime - (globalTestTimeout + 240);
// We need the currentTimeInEpoch so we can check if the tokenRefreshTime is close to expiring.
const currentTimeInEpoch = await this.currentDateToEpoch();
if (tokenRefreshTime <= currentTimeInEpoch) {
return await this.refreshLoginState(umbraco_config_1.umbracoConfig.user.login, umbraco_config_1.umbracoConfig.user.password);
}
return;
}
async getRefreshToken() {
const authToken = await this.getLocalStorageAuthToken();
return authToken.refresh_token;
}
async currentDateToEpoch() {
const currentTime = new Date(Date.now());
return await this.dateToEpoch(currentTime);
}
async dateToEpoch(date) {
const dateToEpoch = date.getTime();
// The epoch is in milliseconds, but we want it to be in seconds(Like it is in the token).
const millisecondsToSeconds = dateToEpoch / 1000;
// There is no need to have anything after .
return Number(millisecondsToSeconds.toString().split('.')[0]);
}
async refreshLoginState(userEmail, userPassword) {
const response = await this.page.request.post(this.baseUrl + '/umbraco/management/api/v1/security/back-office/token', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Cookie: await this.readLocalCookie(),
Origin: this.baseUrl
},
form: {
grant_type: 'refresh_token',
client_id: 'umbraco-back-office',
redirect_uri: this.baseUrl + '/umbraco/oauth_complete',
refresh_token: await this.getRefreshToken()
},
ignoreHTTPSErrors: true
});
if (response.status() === 200) {
const jsonStorageCookie = response.headers()['set-cookie'];
// We get multiple cookies, so we have to split them and then update each of the cookies in our localstorage
let cookies = this.splitCookies(jsonStorageCookie);
for (const cookie of cookies) {
await this.updateCookie(cookie);
}
return;
}
console.log('Error refreshing access token.');
const updatedTokensAndCookie = await this.updateTokenAndCookie(userEmail, userPassword);
console.log('Successfully retrieved new authentication tokens.');
return updatedTokensAndCookie;
}
splitCookies(cookieString) {
return cookieString
.trim()
.split('\n')
.filter(line => line.trim())
.filter(line => !line.includes('expires=Thu, 01 Jan 1970'));
}
async updateTokenAndCookie(userEmail, userPassword) {
const storageStateValues = await this.login.login(userEmail, userPassword);
await this.updateCookie(storageStateValues.cookie);
// We get multiple set cookies, so we have to split them and then update each of the cookies in our localstorage
let cookies = this.splitCookies(storageStateValues.setCookies);
for (const cookie of cookies) {
await this.updateCookie(cookie);
}
let tokens = await this.extractTokensFromSetCookie(storageStateValues.setCookies);
return {
cookie: storageStateValues.cookie,
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
};
}
async readFileContent(filePath) {
try {
const jsonString = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(jsonString);
}
catch (error) {
console.log('Error reading file:', error);
throw error;
}
}
async readLocalBearerToken() {
const filePath = process.env.STORAGE_STAGE_PATH;
if (!filePath) {
return await this.getBearerToken();
}
try {
const data = await JSON.parse(fs.readFileSync(filePath, 'utf-8'));
const localStorageItem = await this.getLocalStorageToken(data, 'umb:userAuthTokenResponse');
const parsedValue = await JSON.parse(localStorageItem.value);
return `Bearer ${parsedValue.access_token}`;
}
catch {
// If the file is not found, return the current access token from the page context
return await this.getBearerToken();
}
}
async getLocalIssuedAtTokenTime() {
const filePath = process.env.STORAGE_STAGE_PATH;
if (!filePath) {
return await this.getTokenIssuedTime();
}
try {
const data = await JSON.parse(fs.readFileSync(filePath, 'utf-8'));
const localStorageItem = await this.getLocalStorageToken(data, 'umb:userAuthTokenResponse');
const value = JSON.parse(localStorageItem.value);
return value.issued_at;
}
catch {
return await this.getTokenIssuedTime();
}
}
async readLocalCookie() {
const filePath = process.env.STORAGE_STAGE_PATH;
if (!filePath) {
return await this.getCookie();
}
try {
const data = await JSON.parse(fs.readFileSync(filePath, 'utf-8'));
return await data.cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('; ') + ';';
}
catch {
// If the file is not found, return the current cookie from the page context
return await this.getCookie();
}
}
async getLocalStorageToken(localStorage, tokenName) {
return await localStorage.origins?.[0]?.localStorage?.find(item => item.name === tokenName);
}
async extractTokensFromSetCookie(setCookies) {
// Extract token values from cookies
const accessToken = setCookies.match(/__Host-umbAccessToken=([^;]+)/)?.[1] ?? "";
const refreshToken = setCookies.match(/__Host-umbRefreshToken=([^;]+)/)?.[1] ?? "";
return { accessToken, refreshToken };
}
async getLocalStorageAuthToken() {
const currentStorageState = await this.page.context().storageState();
const currentStorageToken = await this.getLocalStorageToken(currentStorageState, 'umb:userAuthTokenResponse');
return JSON.parse(currentStorageToken.value);
}
async updateCookie(cookieString) {
try {
// Parse cookie string
const parts = cookieString.split(';').map(p => p.trim());
const [nameValue, ...attributes] = parts;
const [name, value] = nameValue.split('=');
const cookieName = name.trim();
// Get current state
const storageState = await this.page.context().storageState();
const cookieIndex = storageState.cookies.findIndex(c => c.name === cookieName);
if (cookieIndex === -1) {
console.log(`Cookie "${cookieName}" not found`);
return;
}
// Update cookie value
storageState.cookies[cookieIndex].value = value;
// Update expires if present
for (const attr of attributes) {
if (attr.toLowerCase().startsWith('expires=')) {
const expiresDate = attr.split('=')[1];
storageState.cookies[cookieIndex].expires = Date.parse(expiresDate) / 1000;
}
}
// Write to file if path exists
const filePath = process.env.STORAGE_STAGE_PATH;
if (filePath) {
const fs = require('fs');
const fileData = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
const fileCookieIndex = fileData.cookies.findIndex(c => c.name === cookieName);
if (fileCookieIndex !== -1) {
fileData.cookies[fileCookieIndex] = storageState.cookies[cookieIndex];
fs.writeFileSync(filePath, JSON.stringify(fileData, null, 2));
}
else {
console.log(`Cookie "${cookieName}" not found in file`);
}
}
}
catch (error) {
console.error('Error updating cookie:', error);
}
}
async revokeAccessToken(cookie, accessToken) {
return await this.page.request.post(this.baseUrl + '/umbraco/management/api/v1/security/back-office/revoke', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Cookie: cookie,
Origin: this.baseUrl
},
form: {
token: accessToken,
token_type_hint: 'access_token',
client_id: 'umbraco-back-office'
},
ignoreHTTPSErrors: true
});
}
async revokeRefreshToken(cookie, refreshToken) {
return await this.page.request.post(this.baseUrl + '/umbraco/management/api/v1/security/back-office/revoke', {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Cookie: cookie,
Origin: this.baseUrl
},
form: {
token: refreshToken,
token_type_hint: 'refresh_token',
client_id: 'umbraco-back-office'
},
ignoreHTTPSErrors: true
});
}
async loginToAdminUser(testUserCookie, testUserAccessToken, testUserRefreshToken) {
await this.revokeAccessToken(testUserCookie, testUserAccessToken);
await this.revokeRefreshToken(testUserCookie, testUserRefreshToken);
let userCookieAndTokens;
userCookieAndTokens = await this.updateTokenAndCookie(umbraco_config_1.umbracoConfig.user.login, umbraco_config_1.umbracoConfig.user.password);
return userCookieAndTokens;
}
async getCurrentTimePlusMinute(minute = 1) {
const now = new Date();
now.setMinutes(now.getMinutes() + minute); // Add one minute
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
}
async convertDateFormat(dateString) {
return new Date(dateString).toLocaleString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: true,
});
}
}
exports.ApiHelpers = ApiHelpers;
//# sourceMappingURL=ApiHelpers.js.map