@salesforcedevs/sfdocs-e2e 1.0.12-fix-redoc-topic-v-1 → 1.0.12-fix-redoc-topic-v-3
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.
- package/common-wdio.conf.js +4 -8
- package/package.json +1 -1
- package/src/shared/pageobjects/content-action-toolbar.ts +105 -0
- package/src/shared/pageobjects/page.ts +20 -0
- package/src/shared/pageobjects/topic-version.page.ts +23 -1
- package/src/shared/specs/contentActionToolbar.test.ts +121 -29
- package/src/shared/specs/topicVersioning.test.ts +22 -53
package/common-wdio.conf.js
CHANGED
|
@@ -89,14 +89,10 @@ const chrome = resolvedChromeDriverVersion ? { version: resolvedChromeDriverVers
|
|
|
89
89
|
const drivers = {
|
|
90
90
|
// ChromeDriver 115+ is distributed via Chrome for Testing:
|
|
91
91
|
// https://googlechromelabs.github.io/chrome-for-testing/
|
|
92
|
-
chrome
|
|
92
|
+
chrome,
|
|
93
|
+
firefox: { version: '0.35.0' }, // https://github.com/mozilla/geckodriver/releases - 0.30.0+ supports ARM64 Mac
|
|
93
94
|
};
|
|
94
95
|
|
|
95
|
-
// selenium-standalone's own default (4.9.0) doesn't support the `-I` driver flag it
|
|
96
|
-
// unconditionally passes on Selenium 4.x, so the server exits immediately on start.
|
|
97
|
-
// Pinning a version that does support it (4.18.1) avoids that crash.
|
|
98
|
-
const seleniumServerVersion = process.env.SELENIUM_VERSION || '4.18.1';
|
|
99
|
-
|
|
100
96
|
exports.config = {
|
|
101
97
|
//
|
|
102
98
|
// ====================
|
|
@@ -192,8 +188,8 @@ exports.config = {
|
|
|
192
188
|
[
|
|
193
189
|
'selenium-standalone',
|
|
194
190
|
{
|
|
195
|
-
installArgs: {
|
|
196
|
-
args: {
|
|
191
|
+
installArgs: { drivers }, // drivers to install
|
|
192
|
+
args: { drivers }, // drivers to use
|
|
197
193
|
},
|
|
198
194
|
],
|
|
199
195
|
],
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
class ContentActionToolbar {
|
|
2
|
+
HOST_SELECTOR = 'doc-content-action-toolbar';
|
|
3
|
+
INLINE_BUTTON_SELECTOR = 'dx-button.toolbar-button';
|
|
4
|
+
MENU_TRIGGER_SELECTOR = 'dx-button.menu-trigger';
|
|
5
|
+
MENU_SELECTOR = 'dx-dropdown.toolbar-menu';
|
|
6
|
+
MENU_GROUP_SELECTOR = 'div.menu_option';
|
|
7
|
+
MENU_HEADING_SELECTOR = 'span.menu_heading';
|
|
8
|
+
MENU_OPTION_SELECTOR = 'dx-dropdown-option';
|
|
9
|
+
OPTION_LABEL_SELECTOR = 'div.option_label';
|
|
10
|
+
COPIED_TOOLTIP_SELECTOR = '[role="tooltip"].is-visible';
|
|
11
|
+
|
|
12
|
+
async get(): Promise<WebdriverIO.Element> {
|
|
13
|
+
const toolbar = await $(`>>>${this.HOST_SELECTOR}`);
|
|
14
|
+
await toolbar.waitForDisplayed();
|
|
15
|
+
return toolbar;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async isPresent(): Promise<boolean> {
|
|
19
|
+
return (await $(`>>>${this.HOST_SELECTOR}`)).isExisting();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getInlineButtonLabels(toolbar: WebdriverIO.Element): Promise<string[]> {
|
|
23
|
+
const inlineButtons = await toolbar.shadow$$(this.INLINE_BUTTON_SELECTOR);
|
|
24
|
+
return Promise.all(inlineButtons.map(async (button) => (await button.getText()).trim()));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async openMoreActionsMenu(toolbar: WebdriverIO.Element): Promise<void> {
|
|
28
|
+
const trigger = await toolbar.shadow$(this.MENU_TRIGGER_SELECTOR);
|
|
29
|
+
await trigger.waitForClickable();
|
|
30
|
+
await trigger.click();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getMenuOptionLabelsByGroup(
|
|
34
|
+
toolbar: WebdriverIO.Element,
|
|
35
|
+
): Promise<Record<string, string[]>> {
|
|
36
|
+
const dropdown = await toolbar.shadow$(this.MENU_SELECTOR);
|
|
37
|
+
const groupContainers = await dropdown.shadow$$(this.MENU_GROUP_SELECTOR);
|
|
38
|
+
|
|
39
|
+
const labelsByGroup: Record<string, string[]> = {};
|
|
40
|
+
for (const group of groupContainers) {
|
|
41
|
+
// getText() applies the heading's text-transform: uppercase.
|
|
42
|
+
const heading = String(
|
|
43
|
+
(await (await group.$(this.MENU_HEADING_SELECTOR)).getProperty('textContent')) ??
|
|
44
|
+
'',
|
|
45
|
+
).trim();
|
|
46
|
+
const options = await group.$$(this.MENU_OPTION_SELECTOR);
|
|
47
|
+
labelsByGroup[heading] = await Promise.all(
|
|
48
|
+
options.map(async (option) =>
|
|
49
|
+
(await (await option.shadow$(this.OPTION_LABEL_SELECTOR)).getText()).trim(),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return labelsByGroup;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async clickMenuOption(toolbar: WebdriverIO.Element, label: string): Promise<void> {
|
|
57
|
+
const dropdown = await toolbar.shadow$(this.MENU_SELECTOR);
|
|
58
|
+
const options = await dropdown.shadow$$(this.MENU_OPTION_SELECTOR);
|
|
59
|
+
for (const option of options) {
|
|
60
|
+
const optionLabel = (
|
|
61
|
+
await (await option.shadow$(this.OPTION_LABEL_SELECTOR)).getText()
|
|
62
|
+
).trim();
|
|
63
|
+
if (optionLabel === label) {
|
|
64
|
+
await option.click();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Menu option "${label}" not found`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async getUrlFromNewTab(action: () => Promise<void>): Promise<string> {
|
|
72
|
+
const originalHandle = await browser.getWindowHandle();
|
|
73
|
+
const handlesBefore = await browser.getWindowHandles();
|
|
74
|
+
|
|
75
|
+
await action();
|
|
76
|
+
|
|
77
|
+
await browser.waitUntil(
|
|
78
|
+
async (): Promise<boolean> =>
|
|
79
|
+
(await browser.getWindowHandles()).length > handlesBefore.length,
|
|
80
|
+
{
|
|
81
|
+
timeout: 10000,
|
|
82
|
+
timeoutMsg: 'Expected a new browser tab to open',
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const handlesAfter = await browser.getWindowHandles();
|
|
87
|
+
const newHandle = handlesAfter.find((handle) => !handlesBefore.includes(handle));
|
|
88
|
+
if (!newHandle) {
|
|
89
|
+
throw new Error('New browser tab opened but its handle could not be resolved');
|
|
90
|
+
}
|
|
91
|
+
await browser.switchToWindow(newHandle);
|
|
92
|
+
const url = await browser.getUrl();
|
|
93
|
+
|
|
94
|
+
await browser.closeWindow();
|
|
95
|
+
await browser.switchToWindow(originalHandle);
|
|
96
|
+
return url;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getVisibleCopiedTooltipLabel(): Promise<string> {
|
|
100
|
+
const tooltip = await $(`>>>${this.COPIED_TOOLTIP_SELECTOR}`);
|
|
101
|
+
return (await tooltip.getAttribute('aria-label'))?.trim() ?? '';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default new ContentActionToolbar();
|
|
@@ -111,6 +111,26 @@ export default class Page {
|
|
|
111
111
|
return (await label.getText()).trim();
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Opens a parent row without navigating. The icon toggles, so skip the click when
|
|
116
|
+
* the row is already open. WebDriver's click throws on the SVG inside dx-icon, so
|
|
117
|
+
* click through the DOM instead.
|
|
118
|
+
*/
|
|
119
|
+
async expandTreeItem(treeItem: WebdriverIO.Element): Promise<void> {
|
|
120
|
+
const tile = await this.getTreeItemTile(treeItem);
|
|
121
|
+
const icon = await tile.shadow$('.tile-expand-icon dx-icon');
|
|
122
|
+
if ((await icon.getAttribute('aria-expanded')) !== 'true') {
|
|
123
|
+
await browser.execute((expandIcon: { click: () => void }) => {
|
|
124
|
+
expandIcon.click();
|
|
125
|
+
}, icon);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await browser.waitUntil(async () => (await treeItem.shadow$$('dx-tree-item')).length > 0, {
|
|
129
|
+
timeout: 5000,
|
|
130
|
+
timeoutMsg: 'Expected the tree item to expand and render its children',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
114
134
|
async findTreeItemByLabel(
|
|
115
135
|
treeItem: WebdriverIO.Element,
|
|
116
136
|
label: string,
|
|
@@ -63,6 +63,23 @@ export default class TopicVersionPage extends Page {
|
|
|
63
63
|
return wrapper.$('doc-version-picker');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
async openRedocFromSidebar(label: string): Promise<void> {
|
|
67
|
+
const item = await this.getSidebarTreeItem(label);
|
|
68
|
+
await item.click();
|
|
69
|
+
await this.waitForRedocTopicLoad();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async selectRedocVersionByLabel(label: string): Promise<void> {
|
|
73
|
+
const picker = await this.getRedocPicker();
|
|
74
|
+
await this.selectVersionByLabel(picker!, label);
|
|
75
|
+
await this.waitForRedocTopicLoad();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async goBackFromRedoc(): Promise<void> {
|
|
79
|
+
await (await this.getRedocBackLink()).click();
|
|
80
|
+
await this.waitForMarkdownTopicLoad();
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
/**
|
|
67
84
|
* The dismissible "Newer Version Available" banner shown on a non-latest Redoc spec version.
|
|
68
85
|
* Reuses the `doc-phase` component, inserted at the top of `.api-content`. Returns null when
|
|
@@ -129,12 +146,17 @@ export default class TopicVersionPage extends Page {
|
|
|
129
146
|
}
|
|
130
147
|
|
|
131
148
|
async getOptionLabel(option: WebdriverIO.Element): Promise<string> {
|
|
132
|
-
const label = await option.shadow$('
|
|
149
|
+
const label = await option.shadow$('.option_label');
|
|
133
150
|
return (await label.getText()).trim();
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
async selectVersionByLabel(picker: WebdriverIO.Element, label: string): Promise<void> {
|
|
137
154
|
await this.openPicker(picker);
|
|
155
|
+
// The menu fades in. isDisplayed is false while its opacity is still 0.
|
|
156
|
+
await browser.waitUntil(async () => (await this.getVersionOptions()).length > 0, {
|
|
157
|
+
timeout: 5000,
|
|
158
|
+
timeoutMsg: 'Expected the version menu to show its options',
|
|
159
|
+
});
|
|
138
160
|
const options = await this.getVersionOptions();
|
|
139
161
|
for (const option of options) {
|
|
140
162
|
if ((await this.getOptionLabel(option)) === label) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import DocsPage from '../pageobjects/docs.page';
|
|
2
2
|
import GuidePage from '../pageobjects/guide.page';
|
|
3
3
|
import { ReferencePage } from '../pageobjects/reference.page';
|
|
4
|
+
import contentActionToolbar from '../pageobjects/content-action-toolbar';
|
|
4
5
|
|
|
5
6
|
const DOCS_MARKDOWN_PATH = '/docs/docs-content-type-example/get-started.html';
|
|
6
7
|
|
|
@@ -10,63 +11,154 @@ const EN_US_MARKDOWN_REFERENCE_PATH =
|
|
|
10
11
|
const NON_MARKDOWN_REFERENCE_PATH = '/docs/test-metamind-oas-api/references/1mkfx9?meta=Summary';
|
|
11
12
|
const NON_EN_US_MARKDOWN_REFERENCE_PATH =
|
|
12
13
|
'/docs/commerce/ja-jp/flex-references-loc/references/manual-ref/commerce-cloud-api-overview.html';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
14
|
+
const INLINE_MARKDOWN_LABELS = ['Copy as Markdown', 'View as Markdown', 'View llms.txt'];
|
|
15
|
+
const MENU_GROUPS = {
|
|
16
|
+
'This Page': [
|
|
17
|
+
'Copy as Markdown',
|
|
18
|
+
'View as Markdown',
|
|
19
|
+
'Copy URL to Markdown',
|
|
20
|
+
'Open in ChatGPT',
|
|
21
|
+
'Open in Claude',
|
|
22
|
+
'Open in Perplexity',
|
|
23
|
+
],
|
|
24
|
+
'All Documentation': ['View llms.txt', 'Copy URL to llms.txt'],
|
|
25
|
+
};
|
|
26
|
+
const AI_ASSISTANTS = [
|
|
27
|
+
{ label: 'Open in ChatGPT', host: 'chatgpt.com' },
|
|
28
|
+
{ label: 'Open in Claude', host: 'claude.ai' },
|
|
29
|
+
{ label: 'Open in Perplexity', host: 'www.perplexity.ai' },
|
|
30
|
+
];
|
|
31
|
+
const LLMS_TXT_PATH = '/docs/llms.txt';
|
|
32
|
+
const COPIED_TOOLTIP_LABEL = 'Copied!';
|
|
25
33
|
|
|
26
34
|
describe('Content action toolbar scenarios', () => {
|
|
27
|
-
it('
|
|
35
|
+
it('should show the inline markdown actions when opening a markdown guide', async () => {
|
|
28
36
|
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
29
37
|
page.open();
|
|
30
38
|
await page.waitPageLoad();
|
|
31
39
|
|
|
32
|
-
const toolbar = await
|
|
33
|
-
await toolbar.
|
|
34
|
-
|
|
40
|
+
const toolbar = await contentActionToolbar.get();
|
|
41
|
+
expect(await contentActionToolbar.getInlineButtonLabels(toolbar)).toEqual(
|
|
42
|
+
expect.arrayContaining(INLINE_MARKDOWN_LABELS),
|
|
43
|
+
);
|
|
35
44
|
});
|
|
36
45
|
|
|
37
|
-
it('
|
|
46
|
+
it('should show the inline markdown actions when opening a markdown reference', async () => {
|
|
38
47
|
const referencePage = new ReferencePage(EN_US_MARKDOWN_REFERENCE_PATH);
|
|
39
48
|
referencePage.open();
|
|
40
49
|
await referencePage.waitForMarkdownBasedReferencePageLoad();
|
|
41
50
|
|
|
42
|
-
const toolbar = await
|
|
43
|
-
await toolbar.
|
|
44
|
-
|
|
51
|
+
const toolbar = await contentActionToolbar.get();
|
|
52
|
+
expect(await contentActionToolbar.getInlineButtonLabels(toolbar)).toEqual(
|
|
53
|
+
expect.arrayContaining(INLINE_MARKDOWN_LABELS),
|
|
54
|
+
);
|
|
45
55
|
});
|
|
46
56
|
|
|
47
|
-
it('
|
|
57
|
+
it('should show the inline markdown actions when opening a markdown docs page', async () => {
|
|
48
58
|
const page = new DocsPage(DOCS_MARKDOWN_PATH);
|
|
49
59
|
page.open();
|
|
50
60
|
await page.waitPageLoad();
|
|
51
61
|
|
|
52
|
-
const toolbar = await
|
|
53
|
-
await toolbar.
|
|
54
|
-
|
|
62
|
+
const toolbar = await contentActionToolbar.get();
|
|
63
|
+
expect(await contentActionToolbar.getInlineButtonLabels(toolbar)).toEqual(
|
|
64
|
+
expect.arrayContaining(INLINE_MARKDOWN_LABELS),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should expose the full grouped action set through the More actions menu on a markdown page', async () => {
|
|
69
|
+
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
70
|
+
page.open();
|
|
71
|
+
await page.waitPageLoad();
|
|
72
|
+
|
|
73
|
+
const toolbar = await contentActionToolbar.get();
|
|
74
|
+
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
75
|
+
|
|
76
|
+
const labelsByGroup = await contentActionToolbar.getMenuOptionLabelsByGroup(toolbar);
|
|
77
|
+
expect(Object.keys(labelsByGroup)).toEqual(
|
|
78
|
+
expect.arrayContaining(Object.keys(MENU_GROUPS)),
|
|
79
|
+
);
|
|
80
|
+
for (const [group, expectedLabels] of Object.entries(MENU_GROUPS)) {
|
|
81
|
+
expect(labelsByGroup[group]).toEqual(expect.arrayContaining(expectedLabels));
|
|
82
|
+
}
|
|
55
83
|
});
|
|
56
84
|
|
|
57
|
-
|
|
85
|
+
AI_ASSISTANTS.forEach(({ label, host }) => {
|
|
86
|
+
it(`should open ${host} with the page context when selecting ${label}`, async () => {
|
|
87
|
+
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
88
|
+
page.open();
|
|
89
|
+
await page.waitPageLoad();
|
|
90
|
+
|
|
91
|
+
const toolbar = await contentActionToolbar.get();
|
|
92
|
+
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
93
|
+
|
|
94
|
+
const url = await contentActionToolbar.getUrlFromNewTab(() =>
|
|
95
|
+
contentActionToolbar.clickMenuOption(toolbar, label),
|
|
96
|
+
);
|
|
97
|
+
expect(url).toContain(host);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should open the markdown version of the page in a new tab when selecting View as Markdown', async () => {
|
|
102
|
+
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
103
|
+
page.open();
|
|
104
|
+
await page.waitPageLoad();
|
|
105
|
+
|
|
106
|
+
const toolbar = await contentActionToolbar.get();
|
|
107
|
+
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
108
|
+
|
|
109
|
+
const url = await contentActionToolbar.getUrlFromNewTab(() =>
|
|
110
|
+
contentActionToolbar.clickMenuOption(toolbar, 'View as Markdown'),
|
|
111
|
+
);
|
|
112
|
+
expect(url.endsWith('.md')).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should open the site llms.txt in a new tab when selecting View llms.txt', async () => {
|
|
116
|
+
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
117
|
+
page.open();
|
|
118
|
+
await page.waitPageLoad();
|
|
119
|
+
|
|
120
|
+
const toolbar = await contentActionToolbar.get();
|
|
121
|
+
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
122
|
+
|
|
123
|
+
const url = await contentActionToolbar.getUrlFromNewTab(() =>
|
|
124
|
+
contentActionToolbar.clickMenuOption(toolbar, 'View llms.txt'),
|
|
125
|
+
);
|
|
126
|
+
expect(url.endsWith(LLMS_TXT_PATH)).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should flash the Copied! tooltip when selecting Copy URL to Markdown', async () => {
|
|
130
|
+
const page = new GuidePage(EN_US_GUIDE_PATH);
|
|
131
|
+
page.open();
|
|
132
|
+
await page.waitPageLoad();
|
|
133
|
+
|
|
134
|
+
const toolbar = await contentActionToolbar.get();
|
|
135
|
+
await contentActionToolbar.openMoreActionsMenu(toolbar);
|
|
136
|
+
await contentActionToolbar.clickMenuOption(toolbar, 'Copy URL to Markdown');
|
|
137
|
+
|
|
138
|
+
await browser.waitUntil(
|
|
139
|
+
async () =>
|
|
140
|
+
(await contentActionToolbar.getVisibleCopiedTooltipLabel()) ===
|
|
141
|
+
COPIED_TOOLTIP_LABEL,
|
|
142
|
+
{
|
|
143
|
+
timeout: 5000,
|
|
144
|
+
timeoutMsg: 'Expected the menu tooltip to flash "Copied!"',
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should not show the toolbar on a non-markdown reference', async () => {
|
|
58
150
|
const referencePage = new ReferencePage(NON_MARKDOWN_REFERENCE_PATH);
|
|
59
151
|
referencePage.open();
|
|
60
152
|
await referencePage.waitForSpecBasedReferencePageLoad();
|
|
61
153
|
|
|
62
|
-
|
|
63
|
-
expect(await toolbar.isExisting()).toBeFalsy();
|
|
154
|
+
expect(await contentActionToolbar.isPresent()).toBeFalsy();
|
|
64
155
|
});
|
|
65
156
|
|
|
66
|
-
it(
|
|
157
|
+
it('should not show the toolbar on a non en-us page', async () => {
|
|
67
158
|
const nonEnUsReferencePage = new ReferencePage(NON_EN_US_MARKDOWN_REFERENCE_PATH);
|
|
68
159
|
nonEnUsReferencePage.open();
|
|
69
160
|
await nonEnUsReferencePage.waitForMarkdownBasedReferencePageLoad();
|
|
70
|
-
|
|
161
|
+
|
|
162
|
+
expect(await contentActionToolbar.isPresent()).toBeFalsy();
|
|
71
163
|
});
|
|
72
164
|
});
|
|
@@ -180,9 +180,7 @@ describe('Topic-level versioning > versioned parent with own content, subtopics
|
|
|
180
180
|
await page.selectVersionByLabel(picker!, ERRORS_V1_LABEL);
|
|
181
181
|
await page.waitForMarkdownTopicLoad();
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
await child.click();
|
|
185
|
-
await page.waitForRedocTopicLoad();
|
|
183
|
+
await page.openRedocFromSidebar('Another Spec API');
|
|
186
184
|
|
|
187
185
|
await page.waitForUrlToContain(`${BASE}/v1/new-sample-api`);
|
|
188
186
|
// v1's spec lacks the `GET /errors/{code}` operation that only v2 has — its absence
|
|
@@ -192,16 +190,9 @@ describe('Topic-level versioning > versioned parent with own content, subtopics
|
|
|
192
190
|
});
|
|
193
191
|
|
|
194
192
|
it('should show the parent topic version selected on the nested Redoc page after Back', async () => {
|
|
195
|
-
|
|
196
|
-
await
|
|
197
|
-
await page.
|
|
198
|
-
|
|
199
|
-
const redocPicker = await page.getRedocPicker();
|
|
200
|
-
await page.selectVersionByLabel(redocPicker!, ERRORS_V1_LABEL);
|
|
201
|
-
await page.waitForRedocTopicLoad();
|
|
202
|
-
|
|
203
|
-
await (await page.getRedocBackLink()).click();
|
|
204
|
-
await page.waitForMarkdownTopicLoad();
|
|
193
|
+
await page.openRedocFromSidebar('Another Spec API');
|
|
194
|
+
await page.selectRedocVersionByLabel(ERRORS_V1_LABEL);
|
|
195
|
+
await page.goBackFromRedoc();
|
|
205
196
|
|
|
206
197
|
await page.waitForUrlToContain(`${BASE}/v1/errors-and-rate-limits.html`);
|
|
207
198
|
const parentPicker = await page.getSidebarPicker('Errors and Rate Limits');
|
|
@@ -213,16 +204,9 @@ describe('Topic-level versioning > versioned parent with own content, subtopics
|
|
|
213
204
|
await page.selectVersionByLabel(picker!, ERRORS_V1_LABEL);
|
|
214
205
|
await page.waitForMarkdownTopicLoad();
|
|
215
206
|
|
|
216
|
-
|
|
217
|
-
await
|
|
218
|
-
await page.
|
|
219
|
-
|
|
220
|
-
const redocPicker = await page.getRedocPicker();
|
|
221
|
-
await page.selectVersionByLabel(redocPicker!, ERRORS_V2_LABEL);
|
|
222
|
-
await page.waitForRedocTopicLoad();
|
|
223
|
-
|
|
224
|
-
await (await page.getRedocBackLink()).click();
|
|
225
|
-
await page.waitForMarkdownTopicLoad();
|
|
207
|
+
await page.openRedocFromSidebar('Another Spec API');
|
|
208
|
+
await page.selectRedocVersionByLabel(ERRORS_V2_LABEL);
|
|
209
|
+
await page.goBackFromRedoc();
|
|
226
210
|
|
|
227
211
|
await page.waitForUrlToEndWith(`${BASE}/errors-and-rate-limits.html`);
|
|
228
212
|
const parentPicker = await page.getSidebarPicker('Errors and Rate Limits');
|
|
@@ -235,16 +219,9 @@ describe('Topic-level versioning > versioned parent with own content, subtopics
|
|
|
235
219
|
await markdownChild.click();
|
|
236
220
|
await page.waitForMarkdownTopicLoad();
|
|
237
221
|
|
|
238
|
-
|
|
239
|
-
await
|
|
240
|
-
await page.
|
|
241
|
-
|
|
242
|
-
const redocPicker = await page.getRedocPicker();
|
|
243
|
-
await page.selectVersionByLabel(redocPicker!, ERRORS_V1_LABEL);
|
|
244
|
-
await page.waitForRedocTopicLoad();
|
|
245
|
-
|
|
246
|
-
await (await page.getRedocBackLink()).click();
|
|
247
|
-
await page.waitForMarkdownTopicLoad();
|
|
222
|
+
await page.openRedocFromSidebar('Another Spec API');
|
|
223
|
+
await page.selectRedocVersionByLabel(ERRORS_V1_LABEL);
|
|
224
|
+
await page.goBackFromRedoc();
|
|
248
225
|
|
|
249
226
|
await page.waitForUrlToContain(`${BASE}/v1/extra-child-a.html`);
|
|
250
227
|
const siblingChild = await page.getSidebarTreeItem('Common Error Codes');
|
|
@@ -258,25 +235,22 @@ describe('Topic-level versioning > versioned parent with own content, subtopics
|
|
|
258
235
|
page.open();
|
|
259
236
|
await page.waitForMarkdownTopicLoad();
|
|
260
237
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
await page.
|
|
238
|
+
// The spec row is absent until its parent is expanded. Expanding does not
|
|
239
|
+
// navigate, so Get Started stays the referrer.
|
|
240
|
+
const parent = await page.getSidebarTreeItem('Errors and Rate Limits');
|
|
241
|
+
await page.expandTreeItem(parent);
|
|
264
242
|
|
|
265
|
-
|
|
266
|
-
await page.
|
|
267
|
-
await page.
|
|
268
|
-
|
|
269
|
-
await (await page.getRedocBackLink()).click();
|
|
270
|
-
await page.waitForMarkdownTopicLoad();
|
|
243
|
+
await page.openRedocFromSidebar('Another Spec API');
|
|
244
|
+
await page.selectRedocVersionByLabel(ERRORS_V1_LABEL);
|
|
245
|
+
await page.goBackFromRedoc();
|
|
271
246
|
|
|
272
247
|
await page.waitForUrlToContain(`${BASE}/get-started.html`);
|
|
273
248
|
expect(await browser.getUrl()).not.toContain('/v1/');
|
|
274
249
|
});
|
|
275
250
|
|
|
276
251
|
it('should fall back to the project landing page when the Redoc page is opened with no referrer', async () => {
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
// globalThis since the tsconfig's lib list omits "DOM" (see readSessionVersionSelections).
|
|
252
|
+
// No stored back target. WebDriver navigation also leaves document.referrer empty.
|
|
253
|
+
// globalThis: this package's tsconfig lib list omits DOM.
|
|
280
254
|
await browser.execute(() =>
|
|
281
255
|
(globalThis as any).sessionStorage.removeItem('redoc-back-target'),
|
|
282
256
|
);
|
|
@@ -285,15 +259,10 @@ describe('Topic-level versioning > versioned parent with own content, subtopics
|
|
|
285
259
|
page.open();
|
|
286
260
|
await page.waitForRedocTopicLoad();
|
|
287
261
|
|
|
288
|
-
|
|
289
|
-
await page.
|
|
290
|
-
await page.waitForRedocTopicLoad();
|
|
291
|
-
|
|
292
|
-
await (await page.getRedocBackLink()).click();
|
|
293
|
-
await page.waitForMarkdownTopicLoad();
|
|
262
|
+
await page.selectRedocVersionByLabel(ERRORS_V1_LABEL);
|
|
263
|
+
await page.goBackFromRedoc();
|
|
294
264
|
|
|
295
|
-
//
|
|
296
|
-
// (headerHref) — and it's unversioned, so no version segment should leak onto it either.
|
|
265
|
+
// With no back target, Back goes to the first TOC topic. That topic is unversioned.
|
|
297
266
|
await page.waitForUrlToContain(`${BASE}/get-started.html`);
|
|
298
267
|
expect(await browser.getUrl()).not.toContain('/v1/');
|
|
299
268
|
});
|