Skip to content

Commit d2ca5cc

Browse files
authored
Moved more publishing browser tests to e2e suite (#27020)
ref https://linear.app/ghost/issue/PLA-10/ Migrated additional publishing tests into `/e2e/`.
1 parent c29df56 commit d2ca5cc

File tree

5 files changed

+148
-72
lines changed

5 files changed

+148
-72
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export {PostEditorPage} from './post-editor-page';
1+
export {PageEditorPage, PostEditorPage} from './post-editor-page';
22
export {PostPreviewModal} from './post-preview-modal';
33
export {DesktopPreviewFrame, EmailPreviewFrame} from './post-preview-frames';

e2e/helpers/pages/admin/posts/post/post-editor-page.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class SettingsMenu extends BasePage {
77
readonly postUrlInput: Locator;
88
readonly publishDateInput: Locator;
99
readonly publishTimeInput: Locator;
10+
readonly customExcerptInput: Locator;
1011
readonly deletePostButton: Locator;
1112
readonly deletePostConfirmButton: Locator;
1213

@@ -16,6 +17,7 @@ class SettingsMenu extends BasePage {
1617
this.postUrlInput = page.getByRole('textbox', {name: 'Post URL'});
1718
this.publishDateInput = page.getByLabel('Date Picker');
1819
this.publishTimeInput = page.getByLabel('Time Picker');
20+
this.customExcerptInput = page.locator('[data-test-field="custom-excerpt"]');
1921
this.deletePostButton = page.locator('[data-test-button="delete-post"]');
2022
this.deletePostConfirmButton = page.locator('[data-test-button="delete-post-confirm"]');
2123
}
@@ -30,6 +32,10 @@ class PublishFlow extends BasePage {
3032
readonly publishButton: Locator;
3133
readonly publishTypeSetting: Locator;
3234
readonly publishTypeButton: Locator;
35+
readonly publishAtButton: Locator;
36+
readonly scheduleSummary: Locator;
37+
readonly scheduleDateInput: Locator;
38+
readonly scheduleTimeInput: Locator;
3339
readonly emailRecipientsSetting: Locator;
3440
readonly continueButton: Locator;
3541
readonly confirmButton: Locator;
@@ -42,6 +48,10 @@ class PublishFlow extends BasePage {
4248
this.publishButton = page.locator('[data-test-button="publish-flow"]').first();
4349
this.publishTypeSetting = page.locator('[data-test-setting="publish-type"]');
4450
this.publishTypeButton = this.publishTypeSetting.locator('> button');
51+
this.publishAtButton = page.locator('[data-test-setting="publish-at"] > button');
52+
this.scheduleSummary = page.locator('[data-test-setting="publish-at"] [data-test-setting-title]');
53+
this.scheduleDateInput = page.locator('[data-test-date-time-picker-date-input]');
54+
this.scheduleTimeInput = page.locator('[data-test-date-time-picker-time-input]');
4555
this.emailRecipientsSetting = page.locator('[data-test-setting="email-recipients"]');
4656
this.continueButton = page.locator('[data-test-modal="publish-flow"] [data-test-button="continue"]');
4757
this.confirmButton = page.locator('[data-test-modal="publish-flow"] [data-test-button="confirm-publish"]');
@@ -62,6 +72,26 @@ class PublishFlow extends BasePage {
6272
await this.page.locator(`[data-test-publish-type="${type}"] + label`).click();
6373
}
6474

75+
async schedule({date, time}: {date?: string; time?: string}): Promise<void> {
76+
await this.publishAtButton.click();
77+
78+
const textBeforeScheduleToggle = await this.scheduleSummary.textContent();
79+
await this.page.locator('[data-test-radio="schedule"] + label').click();
80+
await this.waitForScheduleSummaryChange(textBeforeScheduleToggle);
81+
82+
if (date) {
83+
const textBeforeDateChange = await this.scheduleSummary.textContent();
84+
await this.scheduleDateInput.fill(date);
85+
await this.scheduleDateInput.blur();
86+
await this.waitForScheduleSummaryChange(textBeforeDateChange);
87+
}
88+
89+
if (time) {
90+
await this.scheduleTimeInput.fill(time);
91+
await this.scheduleTimeInput.blur();
92+
}
93+
}
94+
6595
async confirm(): Promise<void> {
6696
await this.continueButton.click();
6797
await this.confirmButton.click({force: true});
@@ -75,6 +105,14 @@ class PublishFlow extends BasePage {
75105
]);
76106
return frontendPage;
77107
}
108+
109+
private async waitForScheduleSummaryChange(previousText: string | null): Promise<void> {
110+
await this.page.waitForFunction((text) => {
111+
const element = document.querySelector('[data-test-setting="publish-at"] [data-test-setting-title]');
112+
const currentText = element?.textContent?.trim();
113+
return Boolean(currentText && currentText !== text?.trim());
114+
}, previousText);
115+
}
78116
}
79117

80118
export class PostEditorPage extends AdminPage {
@@ -87,14 +125,15 @@ export class PostEditorPage extends AdminPage {
87125
readonly screenTitle: Locator;
88126
readonly lexicalEditor: Locator;
89127
readonly secondaryEditor: Locator;
128+
readonly publishSaveButton: Locator;
90129

91130
readonly settingsMenu: SettingsMenu;
92131

93132
constructor(page: Page) {
94133
super(page);
95134
this.pageUrl = '/ghost/#/editor/post/';
96135

97-
this.titleInput = page.getByRole('textbox', {name: 'Post title'});
136+
this.titleInput = page.locator('[data-test-editor-title-input]');
98137
this.postStatus = page.locator('[data-test-editor-post-status]');
99138
this.previewButton = page.getByRole('button', {name: 'Preview'});
100139
this.previewModal = new PostPreviewModal(page);
@@ -103,6 +142,7 @@ export class PostEditorPage extends AdminPage {
103142
this.screenTitle = page.locator('[data-test-screen-title]');
104143
this.lexicalEditor = page.locator('[data-kg="editor"]').first();
105144
this.secondaryEditor = page.locator('[data-secondary-instance="true"]');
145+
this.publishSaveButton = page.locator('[data-test-button="publish-save"]').first();
106146

107147
this.settingsMenu = new SettingsMenu(page);
108148
}
@@ -141,7 +181,28 @@ export class PostEditorPage extends AdminPage {
141181
await this.postStatus.filter({hasText: /Saved/}).waitFor({timeout: 30000});
142182
}
143183

184+
async appendToBody(text: string): Promise<void> {
185+
await this.lexicalEditor.click();
186+
await this.page.keyboard.type(text);
187+
}
188+
144189
get previewModalDesktopFrame(): DesktopPreviewFrame {
145190
return this.previewModal.desktopPreview;
146191
}
147192
}
193+
194+
export class PageEditorPage extends PostEditorPage {
195+
readonly newPageButton: Locator;
196+
197+
constructor(page: Page) {
198+
super(page);
199+
this.pageUrl = '/ghost/#/pages';
200+
this.newPageButton = page.locator('[data-test-new-page-button]');
201+
}
202+
203+
async gotoNew(): Promise<void> {
204+
await this.page.goto(this.pageUrl);
205+
await this.newPageButton.click();
206+
await this.titleInput.waitFor({state: 'visible'});
207+
}
208+
}

e2e/helpers/pages/public/post-page.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export class PostPage extends PublicPage {
66
readonly postTitle: Locator;
77
readonly postContent: Locator;
88
readonly articleTitle: Locator;
9+
readonly articleHeader: Locator;
910
readonly articleBody: Locator;
11+
readonly metaDescription: Locator;
1012
readonly commentsSection: CommentsSection;
1113
readonly transistorCard: Locator;
1214
readonly transistorIframe: Locator;
@@ -17,7 +19,9 @@ export class PostPage extends PublicPage {
1719
this.postTitle = page.locator('article h1').first();
1820
this.postContent = page.locator('article.gh-article');
1921
this.articleTitle = page.locator('.gh-article-title');
22+
this.articleHeader = page.locator('main > article > header');
2023
this.articleBody = page.locator('.gh-content.gh-canvas > p');
24+
this.metaDescription = page.locator('meta[name="description"]');
2125
this.commentsSection = new CommentsSection(page);
2226
this.transistorCard = page.locator('.kg-transistor-card');
2327
this.transistorIframe = page.locator('iframe[data-kg-transistor-embed]');

e2e/tests/admin/posts/publishing.test.ts

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {APIRequestContext} from '@playwright/test';
2-
import {PostEditorPage, PostsPage} from '@/admin-pages';
1+
import {APIRequestContext, Page} from '@playwright/test';
2+
import {PageEditorPage, PostEditorPage, PostsPage} from '@/admin-pages';
33
import {PostPage} from '@/helpers/pages';
44
import {createMemberFactory, generateSlug} from '@/data-factory';
55
import {expect, test} from '@/helpers/playwright';
@@ -10,6 +10,23 @@ async function getNewsletters(request: APIRequestContext): Promise<string[]> {
1010
return data.newsletters.map((n: {id: string}) => n.id);
1111
}
1212

13+
async function expectFrontendStatus(page: Page, slug: string, status: number, timeout = 20000) {
14+
await expect.poll(async () => {
15+
const response = await page.request.get(`/${slug}/`);
16+
return response.status();
17+
}, {
18+
timeout
19+
}).toBe(status);
20+
}
21+
22+
function formatFrontendDate(date: Date): string {
23+
return new Intl.DateTimeFormat('en-GB', {
24+
day: 'numeric',
25+
month: 'short',
26+
year: 'numeric'
27+
}).format(date);
28+
}
29+
1330
test.describe('Ghost Admin - Publishing', () => {
1431
test.use({mailgunEnabled: true});
1532

@@ -87,6 +104,68 @@ test.describe('Ghost Admin - Publishing', () => {
87104
const response = await page.goto(`/${slug}/`);
88105
expect(response?.status()).toBe(404);
89106
});
107+
108+
test('publish only - page is visible on frontend', async ({page}) => {
109+
const title = `publish-page-only-${Date.now()}`;
110+
const body = 'This is my published page body.';
111+
const editor = new PageEditorPage(page);
112+
113+
await editor.gotoNew();
114+
await editor.createDraft({title, body});
115+
116+
await editor.publishFlow.open();
117+
await editor.publishFlow.confirm();
118+
await editor.publishFlow.close();
119+
120+
await expect(editor.postStatus.first()).toContainText('Published');
121+
await expectFrontendStatus(page, generateSlug(title), 200);
122+
123+
const frontendPage = await page.context().newPage();
124+
const publicPage = new PostPage(frontendPage);
125+
126+
await publicPage.gotoPost(generateSlug(title));
127+
await expect(publicPage.articleTitle).toHaveText(title);
128+
await expect(publicPage.articleBody).toHaveText(body);
129+
});
130+
131+
test('updates a published post', async ({page}) => {
132+
const title = `publish-update-post-${Date.now()}`;
133+
const initialBody = 'This is the initial published text.';
134+
const appendedBodyText = 'This is some updated text.';
135+
const customExcerpt = 'Short description and meta';
136+
const editor = new PostEditorPage(page);
137+
138+
await editor.goto();
139+
await editor.createDraft({title, body: initialBody});
140+
141+
await editor.publishFlow.open();
142+
await editor.publishFlow.confirm();
143+
const frontendPage = await editor.publishFlow.openPublishedPost();
144+
await editor.publishFlow.close();
145+
146+
const publicPage = new PostPage(frontendPage);
147+
await expect(publicPage.articleTitle).toHaveText(title);
148+
await expect(publicPage.articleBody).toContainText(initialBody);
149+
await expect(publicPage.articleHeader).toContainText(formatFrontendDate(new Date()));
150+
151+
const postsPage = new PostsPage(page);
152+
await postsPage.goto();
153+
await postsPage.getPostByTitle(title).click();
154+
155+
await editor.appendToBody(` ${appendedBodyText}`);
156+
await editor.settingsToggleButton.click();
157+
await editor.settingsMenu.publishDateInput.fill('2022-01-07');
158+
await editor.settingsMenu.customExcerptInput.fill(customExcerpt);
159+
160+
await expect(editor.publishSaveButton).toHaveText('Update');
161+
await editor.publishSaveButton.click();
162+
await expect(editor.publishSaveButton).toHaveText('Updated');
163+
164+
await frontendPage.reload();
165+
await expect(publicPage.articleBody).toContainText(appendedBodyText);
166+
await expect(publicPage.articleHeader).toContainText('7 Jan 2022');
167+
await expect(publicPage.metaDescription).toHaveAttribute('content', customExcerpt);
168+
});
90169
});
91170

92171
test.describe('Ghost Admin - Deleting Posts', () => {

ghost/core/test/e2e-browser/admin/publishing.spec.js

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -201,27 +201,7 @@ const openPublishedPostBookmark = async (page) => {
201201
};
202202

203203
test.describe('Publishing', () => {
204-
// Publish post tests moved to e2e/tests/admin/posts/publishing.test.ts
205-
206204
test.describe('Publish page', () => {
207-
// A page can be published and become visible on web
208-
test('Immediately', async ({sharedPage}) => {
209-
const pageData = {
210-
// Title should be unique to avoid slug duplicates
211-
title: 'Published page test',
212-
body: 'This is my scheduled page body.'
213-
};
214-
215-
await sharedPage.goto('/ghost');
216-
await createPage(sharedPage, pageData);
217-
await publishPost(sharedPage, {type: null});
218-
await closePublishFlow(sharedPage);
219-
await checkPostStatus(sharedPage, 'Published');
220-
221-
// Check published
222-
await checkPostPublished(sharedPage, pageData);
223-
});
224-
225205
// page should be published at the scheduled time
226206
test('At the scheduled time', async ({sharedPage}) => {
227207
const pageData = {
@@ -248,54 +228,6 @@ test.describe('Publishing', () => {
248228
});
249229
});
250230

251-
// Lexical rendering tests moved to e2e/tests/admin/posts/lexical-editor.test.ts
252-
253-
test.describe('Update post', () => {
254-
test.describe.configure({retries: 1});
255-
256-
test('Can update a published post', async ({sharedPage: adminPage}) => {
257-
await adminPage.goto('/ghost');
258-
259-
const date = DateTime.now();
260-
261-
await createPostDraft(adminPage, {title: 'Testing publish update', body: 'This is the initial published text.'});
262-
const editorUrl = await adminPage.url();
263-
await publishPost(adminPage);
264-
const frontendPage = await openPublishedPostBookmark(adminPage);
265-
await closePublishFlow(adminPage);
266-
const publishedBody = frontendPage.locator('main > article > section > p');
267-
const publishedHeader = frontendPage.locator('main > article > header');
268-
269-
// check front-end post has the initial body text
270-
await expect(publishedBody).toContainText('This is the initial published text.');
271-
await expect(publishedHeader).toContainText(date.toFormat('LLL d, yyyy'));
272-
273-
// add some extra text to the post
274-
await adminPage.goto(editorUrl);
275-
await adminPage.locator('[data-kg="editor"]').first().click();
276-
await adminPage.waitForTimeout(500);
277-
await adminPage.keyboard.type(' This is some updated text.');
278-
279-
// change some post settings
280-
await openPostSettingsMenu(adminPage);
281-
await adminPage.fill('[data-test-date-time-picker-date-input]', '2022-01-07');
282-
await adminPage.fill('[data-test-field="custom-excerpt"]', 'Short description and meta');
283-
284-
// save
285-
const saveButton = await adminPage.locator('[data-test-button="publish-save"]').first();
286-
await expect(saveButton).toHaveText('Update');
287-
await saveButton.click();
288-
await expect(saveButton).toHaveText('Updated');
289-
290-
// check front-end has new text after reloading
291-
await frontendPage.reload();
292-
await expect(publishedBody).toContainText('This is some updated text.');
293-
await expect(publishedHeader).toContainText('Jan 7, 2022');
294-
const metaDescription = frontendPage.locator('meta[name="description"]');
295-
await expect(metaDescription).toHaveAttribute('content', 'Short description and meta');
296-
});
297-
});
298-
299231
test.describe('Schedule post', () => {
300232
// Post should be published to web and sent as a newsletter at the scheduled time
301233
test('Scheduled Publish and Email', async ({sharedPage}) => {

0 commit comments

Comments
 (0)