@salesforcedevs/sfdocs-e2e 1.0.10 → 1.0.11-toolbar.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/sfdocs-e2e",
3
- "version": "1.0.10",
3
+ "version": "1.0.11-toolbar.1",
4
4
  "description": "E2E test suite for SFDocs static portal pages (SSG)",
5
5
  "license": "ISC",
6
6
  "bin": {
@@ -10,46 +10,17 @@ export class HomePage extends Page {
10
10
  .then((grid) => grid.$$('dx-card-docs'));
11
11
  }
12
12
 
13
- get projectCardsWithGuides(): Promise<WebdriverIO.ElementArray | WebdriverIO.Element[]> {
14
- return this.projectCards.then((cards: WebdriverIO.ElementArray) => {
15
- return cards.filter((card: WebdriverIO.Element) => {
16
- return card.$('dx-button[href*=guide]').then((guideLink) => guideLink.isExisting());
17
- });
18
- });
19
- }
20
-
21
- get projectCardsWithReferences(): Promise<WebdriverIO.ElementArray | WebdriverIO.Element[]> {
22
- return this.projectCards.then((cards) =>
23
- cards.filter((card) =>
24
- card.$('dx-button[href*=references]').then((link) => link.isExisting()),
25
- ),
26
- );
27
- }
28
-
29
- async getProjectCardWithTitle(title: string): Promise<WebdriverIO.Element | undefined> {
13
+ async getProjectCardByTitle(title: string): Promise<WebdriverIO.Element> {
30
14
  const projectCards = await this.projectCards;
31
- for (let i = 0; i < projectCards.length; i++) {
32
- const projectCard = projectCards[i];
33
- const titleValue = await projectCard.getAttribute('header');
34
- if (titleValue === title) {
15
+ for (const projectCard of projectCards) {
16
+ if ((await projectCard.getAttribute('header')) === title) {
35
17
  return projectCard;
36
18
  }
37
19
  }
20
+ throw new Error(
21
+ `Project with title "${title}" is not available, Please check the content-config`,
22
+ );
38
23
  }
39
-
40
- navigateToContentTypeWithProjectTitle =
41
- (projectTitle: string) =>
42
- async (contentType: string): Promise<void> => {
43
- const projectCard = await this.getProjectCardWithTitle(projectTitle);
44
- if (projectCard) {
45
- const contentTypeButton = await projectCard.$(`dx-button[href*=${contentType}]`);
46
- await contentTypeButton.click();
47
- } else {
48
- throw new Error(
49
- `Project with title "${projectTitle}" is not available, Please check the content-config`,
50
- );
51
- }
52
- };
53
24
  }
54
25
 
55
26
  export default new HomePage();
@@ -5,42 +5,28 @@ describe('Guides and References ', () => {
5
5
  beforeEach(async () => {
6
6
  await HomePage.open();
7
7
  });
8
- it('Test if all guides are opening and check the Guide title is same as Guide button text', async () => {
9
- const projectCards = await HomePage.projectCardsWithGuides;
10
-
11
- projectCards.forEach(async (cardElem, index) => {
12
- if (index === 1) {
13
- const guideButton = await cardElem.$('dx-button[href*=guide]');
14
- await guideButton.click();
15
- await DocsContentLayout.waitForContentLayoutSidebar();
16
- const heading = await DocsContentLayout.getSidebarHeading();
17
- expect(heading).toBe('Guides');
18
- }
19
- });
20
- });
21
8
 
22
- it('Test If all references are clickable and check five endpoints URLs with the URLs in Api Description', async () => {
23
- const projectCards = await HomePage.projectCardsWithReferences;
9
+ it('Test that clicking a guides project card opens the guide and shows the guide sidebar', async () => {
10
+ // "Liquid Project" has no overview, so its card links to the guides.
11
+ const projectCard = await HomePage.getProjectCardByTitle('Liquid Project');
12
+ await projectCard.click();
13
+
14
+ await DocsContentLayout.waitForContentLayoutSidebar();
15
+ const sidebarContentTrees = await DocsContentLayout.getSidebarContentTrees();
24
16
 
25
- projectCards.forEach(async (cardElem) => {
26
- const refButton = await cardElem.$('dx-button[href*=references]');
27
- await refButton.click();
28
- (await $('doc-amf-reference')).waitForDisplayed();
29
- const shadowAmfComponent = await (
30
- await $('doc-amf-reference')
31
- ).shadow$('div.container');
32
- const sidebar = await shadowAmfComponent.$('dx-sidebar');
33
- const sidebarTree = await sidebar.shadow$('dx-tree');
34
- const sidebarTreeItems = await sidebarTree.shadow$$('dx-tree-item');
17
+ expect(sidebarContentTrees.length > 0).toBeTruthy();
18
+ });
35
19
 
36
- const endPointsNodes = await sidebarTreeItems[0].shadow$$('dx-tree-item');
37
- const endPointsNode = endPointsNodes[1];
20
+ it('Test that clicking a references project card opens the reference and shows the endpoints', async () => {
21
+ // "Cart APIs" has no overview or guides, so its card links to the references.
22
+ const projectCard = await HomePage.getProjectCardByTitle('Cart APIs');
23
+ await projectCard.click();
38
24
 
39
- const endpointsText = (await endPointsNode.shadow$('a')).getText();
25
+ await (await $('doc-amf-reference')).waitForDisplayed();
40
26
 
41
- expect(endpointsText).toBe('Endpoints');
27
+ await browser.waitUntil(async () => (await $$('>>>dx-tree-item')).length > 0);
28
+ const dxTreeItems = await $$('>>>dx-tree-item');
42
29
 
43
- browser.back();
44
- });
30
+ expect(dxTreeItems.length > 0).toBeTruthy();
45
31
  });
46
32
  });
@@ -13,35 +13,50 @@ describe('Homepage', () => {
13
13
  it('should show the expected number of projects', async () => {
14
14
  const projectCards = await HomePage.projectCards;
15
15
 
16
- expect(projectCards.length).toEqual(21);
16
+ expect(projectCards.length).toEqual(33);
17
17
  });
18
18
 
19
19
  // it('is accessible', () => {
20
20
  // assertAccessibleSync();
21
21
  // });
22
22
 
23
- describe('Check homepage Overview links', () => {
24
- it('Test Overview button in the card', async () => {
25
- const projectCards = await HomePage.projectCards;
26
- const cardElem = projectCards[0];
27
- const overviewButton = await cardElem.$('dx-button[href*=overview]');
23
+ describe('Project card click navigation', () => {
24
+ it('navigates to the overview for a project that has an overview', async () => {
25
+ const projectCard = await HomePage.getProjectCardByTitle(
26
+ 'TEST Commerce Cloud Orders APIs',
27
+ );
28
28
 
29
- expect(await overviewButton.getText()).toMatch('Overview');
30
- expect(await overviewButton.isClickable()).toBeTruthy();
29
+ const href = await projectCard.getAttribute('href');
30
+ expect(href).toContain('/overview');
31
+
32
+ await projectCard.click();
33
+ await (await $('dx-section')).waitForDisplayed();
34
+
35
+ expect(await browser.getUrl()).toContain('/overview');
31
36
  });
32
- });
33
37
 
34
- describe('Check homepage Guide links', () => {
35
- it('Test Guide buttons in the cards', async () => {
36
- const projectCards = await HomePage.projectCardsWithGuides;
37
-
38
- projectCards.forEach(async (cardElem, index) => {
39
- const guideButton = await cardElem.$('dx-button[href*=guide]');
40
- if (index === 0 || index === 1) {
41
- expect(await guideButton.getText()).not.toBe(null);
42
- expect(await guideButton.isClickable()).toBeTruthy();
43
- }
44
- });
38
+ it('navigates to the guides for a project that has no overview but has guides', async () => {
39
+ const projectCard = await HomePage.getProjectCardByTitle('Liquid Project');
40
+
41
+ const href = await projectCard.getAttribute('href');
42
+ expect(href).toContain('/guide');
43
+
44
+ await projectCard.click();
45
+ await (await $('doc-content-layout')).waitForDisplayed();
46
+
47
+ expect(await browser.getUrl()).toContain('/guide');
48
+ });
49
+
50
+ it('navigates to the references for a project that has no overview and guides but has references', async () => {
51
+ const projectCard = await HomePage.getProjectCardByTitle('Cart APIs');
52
+
53
+ const href = await projectCard.getAttribute('href');
54
+ expect(href).toContain('/references');
55
+
56
+ await projectCard.click();
57
+ await (await $('doc-amf-reference')).waitForDisplayed();
58
+
59
+ expect(await browser.getUrl()).toContain('/references');
45
60
  });
46
61
  });
47
62
  });
@@ -9,9 +9,8 @@ describe('Navigation header test', () => {
9
9
  const projectCards = await HomePage.projectCards;
10
10
 
11
11
  const cardElem = projectCards[0];
12
- const overviewButton = await cardElem.$('dx-button[href*=overview]');
13
12
 
14
- await overviewButton.click();
13
+ await cardElem.click();
15
14
  (await $('>>> .header_l2_group-nav_overflow')).waitForDisplayed();
16
15
 
17
16
  const docHeaderNav = await $('>>> .header_l2_group-nav_overflow');
@@ -33,9 +32,8 @@ describe('Navigation header test', () => {
33
32
  const projectCards = await HomePage.projectCards;
34
33
 
35
34
  const cardElem = projectCards[0];
36
- const guideButton = await cardElem.$('dx-button[href*=guide]');
37
35
 
38
- await guideButton.click();
36
+ await cardElem.click();
39
37
  (await $('>>> .header_l2_group-nav_overflow')).waitForDisplayed();
40
38
 
41
39
  const docHeaderNav = await $('>>> .header_l2_group-nav_overflow');
@@ -57,9 +55,8 @@ describe('Navigation header test', () => {
57
55
  const projectCards = await HomePage.projectCards;
58
56
 
59
57
  const cardElem = projectCards[0];
60
- const guideButton = await cardElem.$('dx-button[href*=references]');
61
58
 
62
- await guideButton.click();
59
+ await cardElem.click();
63
60
  (await $('>>> .header_l2_group-nav_overflow')).waitForDisplayed();
64
61
 
65
62
  const docHeaderNav = await $('>>> .header_l2_group-nav_overflow');
@@ -40,6 +40,90 @@ describe('Test Sitemap Routes', () => {
40
40
  );
41
41
  });
42
42
 
43
+ it('lists each docs markdown page once and each spec page without a .html extension', async () => {
44
+ await HomePage.open('sitemap.xml');
45
+ const data = await browser.getSitemap(
46
+ 'http://localhost:3000/internal-sitemap-contents-1.xml',
47
+ );
48
+
49
+ // Markdown topics are published with a .html extension.
50
+ expect(data.body).toContain('<loc>/docs/docs-content-type-example/get-started.html</loc>');
51
+ expect(data.body).toContain(
52
+ '<loc>/docs/docs-content-type-example/widget-concepts.html</loc>',
53
+ );
54
+ expect(data.body).toContain(
55
+ '<loc>/docs/docs-content-type-example/beta-bulk-import.html</loc>',
56
+ );
57
+
58
+ // Spec (Redoc) topics are published without an extension.
59
+ expect(data.body).toContain('<loc>/docs/docs-content-type-example/sample-api</loc>');
60
+ expect(data.body).toContain('<loc>/docs/docs-content-type-example/new-sample-api</loc>');
61
+
62
+ // Each markdown URL appears exactly once.
63
+ const occurrences =
64
+ data.body.split('<loc>/docs/docs-content-type-example/get-started.html</loc>').length -
65
+ 1;
66
+ expect(occurrences).toBe(1);
67
+ });
68
+
69
+ it('excludes the external-link topic from the docs sitemap', async () => {
70
+ await HomePage.open('sitemap.xml');
71
+ const data = await browser.getSitemap(
72
+ 'http://localhost:3000/internal-sitemap-contents-1.xml',
73
+ );
74
+
75
+ // externalLink entries never become routes, so they can't reach the sitemap.
76
+ expect(data.body).not.toContain('developer.salesforce.com');
77
+ });
78
+
79
+ it('lists the deep-nesting docs project pages and spec in the sitemap', async () => {
80
+ await HomePage.open('sitemap.xml');
81
+ const data = await browser.getSitemap(
82
+ 'http://localhost:3000/internal-sitemap-contents-1.xml',
83
+ );
84
+
85
+ expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/introduction.html</loc>');
86
+ expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/install.html</loc>');
87
+ expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/workspace.html</loc>');
88
+ expect(data.body).toContain('<loc>/docs/docs-deep-nesting-example/example-api</loc>');
89
+ });
90
+
91
+ it('lists a localized docs page but omits pages excluded from the ja-jp menu', async () => {
92
+ await HomePage.open('sitemap.xml');
93
+ const data = await browser.getSitemap(
94
+ 'http://localhost:3000/internal-sitemap-contents-1.xml',
95
+ );
96
+
97
+ // ja-jp topics that are configured in the ja-jp TOC are legitimately in the sitemap.
98
+ expect(data.body).toContain(
99
+ '<loc>/docs/ja-jp/docs-content-type-example/get-started.html</loc>',
100
+ );
101
+
102
+ // englishonly pages are never in the ja-jp menu, so they must not appear under /ja-jp/.
103
+ expect(data.body).not.toContain(
104
+ '<loc>/docs/ja-jp/docs-content-type-example/us-compliance-notes.html</loc>',
105
+ );
106
+ });
107
+
108
+ it('serves the noindex robots metatag on the docs home URL but not on a real topic', async () => {
109
+ await browser.addCommand('getHtml', async (url: any) => {
110
+ const data = await got(url);
111
+ return data;
112
+ });
113
+
114
+ // The project home URL renders the first topic as a landing page with noindex.
115
+ const home = await browser.getHtml('http://localhost:3000/docs/docs-content-type-example');
116
+ expect(home.body).toContain('<meta name="robots" content="noindex,nofollow,nosnippet"/>');
117
+
118
+ // The same topic requested at its real URL must be indexable.
119
+ const topic = await browser.getHtml(
120
+ 'http://localhost:3000/docs/docs-content-type-example/get-started.html',
121
+ );
122
+ expect(topic.body).not.toContain(
123
+ '<meta name="robots" content="noindex,nofollow,nosnippet"/>',
124
+ );
125
+ });
126
+
43
127
  it('Read Coveo sitemap.xml', async () => {
44
128
  await browser.addCommand('getCoveoSitemap', async (url: any) => {
45
129
  const data = await got(url, {
@@ -1,6 +1,5 @@
1
1
  import DocsApisPage from '../pageobjects/docs.apis.page';
2
2
  import layoutTest from '../../shared/specs/layout-tests';
3
- import DocsContentLayout from '../../shared/pageobjects/docs-content-layout';
4
3
 
5
4
  let page: DocsApisPage;
6
5
  describe('/docs page basic display', () => {
@@ -23,46 +22,4 @@ describe('/docs page basic display', () => {
23
22
 
24
23
  expect((await cards).length).not.toBe(0);
25
24
  });
26
-
27
- it('should display project cards click on overview - test first overview card', async () => {
28
- const projectCards = await page.projectCardsWithGuides;
29
- projectCards.forEach(async (cardElem, index) => {
30
- if (index === 1) {
31
- const guideButton = await cardElem.$('dx-button[href*=overview]');
32
- await guideButton.click();
33
- await DocsContentLayout.waitForContentLayoutSidebar();
34
- const heading = await DocsContentLayout.getSidebarHeading();
35
-
36
- expect(heading).toBe('Overview');
37
- }
38
- });
39
- });
40
-
41
- it('should display project cards click on guides - test first guide card', async () => {
42
- const projectCards = await page.projectCardsWithGuides;
43
- projectCards.forEach(async (cardElem, index) => {
44
- if (index === 1) {
45
- const guideButton = await cardElem.$('dx-button[href*=guide]');
46
- await guideButton.click();
47
- await DocsContentLayout.waitForContentLayoutSidebar();
48
- const heading = await DocsContentLayout.getSidebarHeading();
49
-
50
- expect(heading).toBe('Guides');
51
- }
52
- });
53
- });
54
-
55
- it('should display project cards click on references - test first references card', async () => {
56
- const projectCards = await page.projectCardsWithReferences;
57
- projectCards.forEach(async (cardElem, index) => {
58
- if (index === 1) {
59
- const refButton = await cardElem.$('dx-button[href*=references]');
60
- await refButton.click();
61
- await DocsContentLayout.waitForContentLayoutSidebar();
62
- const heading = await DocsContentLayout.getSidebarHeading();
63
-
64
- expect(heading).toBe('References');
65
- }
66
- });
67
- });
68
25
  });
@@ -0,0 +1,99 @@
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
+ const heading = (await (await group.$(this.MENU_HEADING_SELECTOR)).getText()).trim();
42
+ const options = await group.$$(this.MENU_OPTION_SELECTOR);
43
+ labelsByGroup[heading] = await Promise.all(
44
+ options.map(async (option) =>
45
+ (await (await option.shadow$(this.OPTION_LABEL_SELECTOR)).getText()).trim(),
46
+ ),
47
+ );
48
+ }
49
+ return labelsByGroup;
50
+ }
51
+
52
+ async clickMenuOption(toolbar: WebdriverIO.Element, label: string): Promise<void> {
53
+ const dropdown = await toolbar.shadow$(this.MENU_SELECTOR);
54
+ const options = await dropdown.shadow$$(this.MENU_OPTION_SELECTOR);
55
+ for (const option of options) {
56
+ const optionLabel = (
57
+ await (await option.shadow$(this.OPTION_LABEL_SELECTOR)).getText()
58
+ ).trim();
59
+ if (optionLabel === label) {
60
+ await option.click();
61
+ return;
62
+ }
63
+ }
64
+ throw new Error(`Menu option "${label}" not found`);
65
+ }
66
+
67
+ async getUrlFromNewTab(action: () => Promise<void>): Promise<string> {
68
+ const originalHandle = await browser.getWindowHandle();
69
+ const handlesBefore = await browser.getWindowHandles();
70
+
71
+ await action();
72
+
73
+ await browser.waitUntil(
74
+ async (): Promise<boolean> =>
75
+ (await browser.getWindowHandles()).length > handlesBefore.length,
76
+ {
77
+ timeout: 10000,
78
+ timeoutMsg: 'Expected a new browser tab to open',
79
+ },
80
+ );
81
+
82
+ const newHandle = (await browser.getWindowHandles()).find(
83
+ (handle) => !handlesBefore.includes(handle),
84
+ ) as string;
85
+ await browser.switchToWindow(newHandle);
86
+ const url = await browser.getUrl();
87
+
88
+ await browser.closeWindow();
89
+ await browser.switchToWindow(originalHandle);
90
+ return url;
91
+ }
92
+
93
+ async getVisibleCopiedTooltipLabel(): Promise<string> {
94
+ const tooltip = await $(`>>>${this.COPIED_TOOLTIP_SELECTOR}`);
95
+ return (await tooltip.getAttribute('aria-label'))?.trim() ?? '';
96
+ }
97
+ }
98
+
99
+ export default new ContentActionToolbar();
@@ -1,16 +1,15 @@
1
1
  class DocsContentLayout {
2
- SIDEBAR_HEADING_SELECTOR = 'div > div.padding-horizontal > div.header > h1.header-title';
2
+ SIDEBAR_SELECTOR = 'div > dx-sidebar-old';
3
+ SIDEBAR_CONTENT_SELECTOR = 'div.container div.sidebar-content dx-tree';
3
4
 
4
5
  async waitForContentLayoutSidebar(): Promise<void> {
5
- const layout = await $('doc-content-layout');
6
- const sidebar = await layout.shadow$('div > dx-sidebar');
6
+ const sidebar = await (await $('doc-content-layout')).shadow$(this.SIDEBAR_SELECTOR);
7
7
  await sidebar.waitForDisplayed();
8
8
  }
9
9
 
10
- async getSidebarHeading(): Promise<string> {
11
- const sidebar = await (await $('doc-content-layout')).shadow$('div > dx-sidebar');
12
- const heading = await sidebar.shadow$(this.SIDEBAR_HEADING_SELECTOR);
13
- return heading.getText();
10
+ async getSidebarContentTrees(): Promise<WebdriverIO.ElementArray> {
11
+ const sidebar = await (await $('doc-content-layout')).shadow$(this.SIDEBAR_SELECTOR);
12
+ return sidebar.shadow$$(this.SIDEBAR_CONTENT_SELECTOR);
14
13
  }
15
14
  }
16
15
 
@@ -0,0 +1,128 @@
1
+ import Page from './page';
2
+
3
+ class DocsPage extends Page {
4
+ constructor(url?: string) {
5
+ super(url ?? '/docs');
6
+ }
7
+
8
+ async waitPageLoad(): Promise<void> {
9
+ await (await $('>>>doc-content-layout')).waitForDisplayed();
10
+ }
11
+
12
+ async waitForRedocLoad(): Promise<void> {
13
+ await $('doc-redoc-reference > .redoc-container').waitForDisplayed({
14
+ timeout: this.REFERENCE_LOAD_WAIT_TIME,
15
+ });
16
+ }
17
+
18
+ async getContentHeading(): Promise<WebdriverIO.Element> {
19
+ return (await $('doc-unified-content-layout')).$('div.markdown-content h1');
20
+ }
21
+
22
+ private async getSidebar(): Promise<WebdriverIO.Element> {
23
+ return (await $('>>>doc-content-layout')).shadow$('div > dx-sidebar-old');
24
+ }
25
+
26
+ async waitForSidebar(): Promise<void> {
27
+ await (await this.getSidebar()).waitForDisplayed();
28
+ }
29
+
30
+ async getSidebarTrees(): Promise<WebdriverIO.ElementArray> {
31
+ const sidebar = await this.getSidebar();
32
+ return sidebar.shadow$$('div.container div.sidebar-content dx-tree');
33
+ }
34
+
35
+ private async getTreeTile(treeItem: WebdriverIO.Element): Promise<WebdriverIO.Element> {
36
+ return treeItem.shadow$('a > dx-tree-tile');
37
+ }
38
+
39
+ private async getTreeItemLabel(treeItem: WebdriverIO.Element): Promise<string> {
40
+ const treeTile = await this.getTreeTile(treeItem);
41
+ const label = await treeTile.shadow$('span.tile-label');
42
+ return (await label.getText()).trim();
43
+ }
44
+
45
+ private async findTreeItemByLabel(
46
+ treeItem: WebdriverIO.Element,
47
+ label: string,
48
+ ): Promise<WebdriverIO.Element | null> {
49
+ if ((await this.getTreeItemLabel(treeItem)) === label) {
50
+ return treeItem;
51
+ }
52
+
53
+ const children = await treeItem.shadow$$('dx-tree-item');
54
+ for (const child of children) {
55
+ const match = await this.findTreeItemByLabel(child, label);
56
+ if (match) {
57
+ return match;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ async getSidebarTile(label: string): Promise<WebdriverIO.Element> {
64
+ const trees = await this.getSidebarTrees();
65
+ for (const tree of trees) {
66
+ const rootItem = await tree.shadow$('dx-tree-item');
67
+ const match = await this.findTreeItemByLabel(rootItem, label);
68
+ if (match) {
69
+ return match;
70
+ }
71
+ }
72
+ throw new Error(`No sidebar tile found with label "${label}"`);
73
+ }
74
+
75
+ async getTileForwardArrow(label: string): Promise<WebdriverIO.Element> {
76
+ const treeItem = await this.getSidebarTile(label);
77
+ return (await this.getTreeTile(treeItem)).shadow$('.tile-forward-arrow');
78
+ }
79
+
80
+ async getTileExternalLinkIcon(label: string): Promise<WebdriverIO.Element> {
81
+ const treeItem = await this.getSidebarTile(label);
82
+ return (await this.getTreeTile(treeItem)).shadow$('.tile-external-link-icon');
83
+ }
84
+
85
+ async getRedocBackLink(): Promise<WebdriverIO.Element> {
86
+ return $('>>>a.redoc-project-back');
87
+ }
88
+
89
+ async getRedocProjectTitle(): Promise<WebdriverIO.Element> {
90
+ return $('>>>.redoc-project-title');
91
+ }
92
+
93
+ async getRedocApiTitle(): Promise<WebdriverIO.Element> {
94
+ return $('>>>div.api-info > h1');
95
+ }
96
+
97
+ async getCurrentBreadcrumbLabel(): Promise<string> {
98
+ const breadcrumbs = await $('>>>doc-breadcrumbs');
99
+ const items = await breadcrumbs.shadow$$('doc-breadcrumb-item');
100
+ const lastItem = items[items.length - 1];
101
+ const label = await lastItem.shadow$('span');
102
+ return (await label.getText()).trim();
103
+ }
104
+
105
+ async getLanguagePicker(): Promise<WebdriverIO.Element> {
106
+ const pickers = await $$('>>>dx-dropdown [aria-label="Select Language"]');
107
+ for (const picker of pickers) {
108
+ if (await picker.isDisplayed()) {
109
+ return picker;
110
+ }
111
+ }
112
+ throw new Error('No visible language picker found');
113
+ }
114
+
115
+ async selectLanguage(displayText: string): Promise<void> {
116
+ const options = await $$('>>>dx-dropdown-option');
117
+ for (const option of options) {
118
+ const link = await option.shadow$('a.option');
119
+ if ((await link.getText()).trim() === displayText) {
120
+ await browser.execute((el: { click: () => void }) => el.click(), link);
121
+ return;
122
+ }
123
+ }
124
+ throw new Error(`No language option found with label "${displayText}"`);
125
+ }
126
+ }
127
+
128
+ export default DocsPage;
@@ -28,22 +28,6 @@ export default class Page {
28
28
  .then((grid) => grid.$$('dx-card-docs'));
29
29
  }
30
30
 
31
- get projectCardsWithGuides(): Promise<WebdriverIO.ElementArray | WebdriverIO.Element[]> {
32
- return this.projectCards.then((cards: WebdriverIO.ElementArray) => {
33
- return cards.filter((card: WebdriverIO.Element) => {
34
- return card.$('dx-button[href*=guide]').then((guideLink) => guideLink.isExisting());
35
- });
36
- });
37
- }
38
-
39
- get projectCardsWithReferences(): Promise<WebdriverIO.ElementArray | WebdriverIO.Element[]> {
40
- return this.projectCards.then((cards) =>
41
- cards.filter((card) =>
42
- card.$('dx-button[href*=references]').then((link) => link.isExisting()),
43
- ),
44
- );
45
- }
46
-
47
31
  async getHeader(headerTag = 'dx-global-header'): Promise<WebdriverIO.Element> {
48
32
  return await $(headerTag);
49
33
  }