ef143978e7
closes https://linear.app/tryghost/issue/ENG-721 ref https://linear.app/tryghost/issue/ENG-708 Comments-UI loads `/ghost/admin-frame/` in an iframe to check if a Staff User is authenticated in order to show moderation options. That iframe request loads a HTML page which in turn contains a script that fires off an API request that attempts to fetch the logged-in user details, resulting in a 403 "error" showing up when not authenticated. In the vast majority of cases there will be no staff user authenticated so lots of extra requests and "errors" are seen unnecessarily. - adjusted the `/ghost/auth-frame/` endpoint to check if the request contains an Admin session cookie - if it does, continue as before with rendering the HTML page so the script is loaded - if it doesn't, return an empty 204 response avoiding the script request and subsequent 403-generating API request - eliminates the 403 error being generated for all typical visitor traffic, the error should only be seen when an Admin was previously logged in but their cookie is no longer valid (either from logging out, or going past the 6month validity period)
205 lines
5.8 KiB
TypeScript
205 lines
5.8 KiB
TypeScript
import {E2E_PORT} from '../../playwright.config';
|
|
import {Locator, Page} from '@playwright/test';
|
|
import {MockedApi} from './MockedApi';
|
|
import {expect} from '@playwright/test';
|
|
|
|
export const MOCKED_SITE_URL = 'https://localhost:1234';
|
|
export {MockedApi};
|
|
|
|
export async function waitEditorFocused(editor: Locator) {
|
|
// Wait for focused
|
|
const internalEditor = editor.getByTestId('editor');
|
|
await expect(internalEditor).toBeFocused();
|
|
}
|
|
|
|
function escapeHtml(unsafe: string) {
|
|
return unsafe
|
|
.replace(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
function authFrameMain() {
|
|
window.addEventListener('message', function (event) {
|
|
let d = null;
|
|
try {
|
|
d = JSON.parse(event.data);
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
|
|
if (!d) {
|
|
return;
|
|
}
|
|
const data: {uid: string, action: string} = d;
|
|
|
|
function respond(error, result) {
|
|
event.source!.postMessage(JSON.stringify({
|
|
uid: data.uid,
|
|
error: error,
|
|
result: result
|
|
}));
|
|
}
|
|
|
|
if (data.action === 'getUser') {
|
|
try {
|
|
respond(null, {
|
|
users: [
|
|
{
|
|
id: 'someone'
|
|
}
|
|
]
|
|
});
|
|
} catch (err) {
|
|
respond(err, null);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Other actions: return empty object
|
|
try {
|
|
respond(null, {});
|
|
} catch (err) {
|
|
respond(err, null);
|
|
}
|
|
});
|
|
}
|
|
|
|
export async function mockAdminAuthFrame({admin, page}) {
|
|
await page.route(admin + 'auth-frame/', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
body: `<html><head><meta charset="UTF-8" /></head><body><script>${authFrameMain.toString()}; authFrameMain();</script></body></html>`
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function mockAdminAuthFrame204({admin, page}) {
|
|
await page.route(admin + 'auth-frame/', async (route) => {
|
|
await route.fulfill({
|
|
status: 204
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function initialize({mockedApi, page, bodyStyle, ...options}: {
|
|
mockedApi: MockedApi,
|
|
page: Page,
|
|
path?: string;
|
|
ghostComments?: string,
|
|
key?: string,
|
|
api?: string,
|
|
admin?: string,
|
|
colorScheme?: string,
|
|
avatarSaturation?: string,
|
|
accentColor?: string,
|
|
commentsEnabled?: string,
|
|
title?: string,
|
|
count?: boolean,
|
|
publication?: string,
|
|
postId?: string,
|
|
bodyStyle?: string,
|
|
}) {
|
|
const sitePath = MOCKED_SITE_URL;
|
|
await page.route(sitePath, async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
body: `<html><head><meta charset="UTF-8" /></head><body ${bodyStyle ? 'style="' + escapeHtml(bodyStyle) + '"' : ''}></body></html>`
|
|
});
|
|
});
|
|
|
|
const url = `http://localhost:${E2E_PORT}/comments-ui.min.js`;
|
|
await page.setViewportSize({width: 1000, height: 1000});
|
|
|
|
await page.goto(sitePath);
|
|
await mockedApi.listen({page, path: sitePath});
|
|
|
|
if (!options.ghostComments) {
|
|
options.ghostComments = MOCKED_SITE_URL;
|
|
}
|
|
|
|
if (!options.postId) {
|
|
options.postId = mockedApi.postId;
|
|
}
|
|
|
|
await page.evaluate((data) => {
|
|
const scriptTag = document.createElement('script');
|
|
scriptTag.src = data.url;
|
|
|
|
for (const option of Object.keys(data.options)) {
|
|
scriptTag.dataset[option] = data.options[option];
|
|
}
|
|
document.body.appendChild(scriptTag);
|
|
}, {url, options});
|
|
|
|
const commentsFrameSelector = 'iframe[title="comments-frame"]';
|
|
|
|
await page.waitForSelector('iframe');
|
|
|
|
// wait for data to be loaded because our tests expect it
|
|
const iframeElement = await page.locator(commentsFrameSelector).elementHandle();
|
|
if (!iframeElement) {
|
|
throw new Error('iframe not found');
|
|
}
|
|
const iframe = await iframeElement.contentFrame();
|
|
if (!iframe) {
|
|
throw new Error('iframe contentFrame not found');
|
|
}
|
|
await iframe.waitForSelector('[data-loaded="true"]');
|
|
|
|
return {
|
|
frame: page.frameLocator(commentsFrameSelector)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Select text range by RegExp.
|
|
*/
|
|
export async function selectText(locator: Locator, pattern: string | RegExp): Promise<void> {
|
|
await locator.evaluate(
|
|
(element, {pattern: p}) => {
|
|
let textNode = element.childNodes[0];
|
|
|
|
while (textNode.nodeType !== Node.TEXT_NODE && textNode.childNodes.length) {
|
|
textNode = textNode.childNodes[0];
|
|
}
|
|
const match = textNode.textContent?.match(new RegExp(p));
|
|
if (match) {
|
|
const range = document.createRange();
|
|
range.setStart(textNode, match.index!);
|
|
range.setEnd(textNode, match.index! + match[0].length);
|
|
const selection = document.getSelection();
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range);
|
|
}
|
|
},
|
|
{pattern}
|
|
);
|
|
}
|
|
|
|
export async function getHeight(locator: Locator) {
|
|
return await locator.evaluate((node) => {
|
|
return node.clientHeight;
|
|
});
|
|
}
|
|
|
|
export async function setClipboard(page, text) {
|
|
const modifier = getModifierKey();
|
|
await page.setContent(`<div contenteditable>${text}</div>`);
|
|
await page.focus('div');
|
|
await page.keyboard.press(`${modifier}+KeyA`);
|
|
await page.keyboard.press(`${modifier}+KeyC`);
|
|
}
|
|
|
|
export function getModifierKey() {
|
|
const os = require('os');
|
|
const platform = os.platform();
|
|
if (platform === 'darwin') {
|
|
return 'Meta';
|
|
} else {
|
|
return 'Control';
|
|
}
|
|
}
|