From b9ad527cd7573ab48e047ada375e66f677d14e42 Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Mon, 25 Sep 2023 21:47:54 +0000 Subject: [PATCH] [Data Explorer][Discover][Functional Test] Fix ciGroup7 Issue Resolve https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5129 Signed-off-by: Anan Zhuang --- .../apps/context/_discover_navigation.js | 5 +- .../management/_index_pattern_results_sort.js | 24 +-- .../_opensearch_dashboards_settings.js | 2 +- .../apps/management/_scripted_fields.js | 171 +++++++++--------- .../management/_scripted_fields_preview.js | 3 +- test/functional/page_objects/discover_page.ts | 77 ++++++++ test/functional/services/data_grid.ts | 53 +++++- 7 files changed, 227 insertions(+), 108 deletions(-) diff --git a/test/functional/apps/context/_discover_navigation.js b/test/functional/apps/context/_discover_navigation.js index 5680b28921a7..c83cf0d70368 100644 --- a/test/functional/apps/context/_discover_navigation.js +++ b/test/functional/apps/context/_discover_navigation.js @@ -80,9 +80,8 @@ export default function ({ getService, getPageObjects }) { }); it('should open the context view with the same columns', async () => { - const data = await dataGrid.getDataGridTableData(); - - expect(data.columns).to.eql(['', 'Time (@timestamp)', ...TEST_COLUMN_NAMES]); + const data = await dataGrid.getHeaderFields(); + expect(data.columns).to.eql(['Time (@timestamp)', ...TEST_COLUMN_NAMES]); }); it('should open the context view with the filters disabled', async () => { diff --git a/test/functional/apps/management/_index_pattern_results_sort.js b/test/functional/apps/management/_index_pattern_results_sort.js index 1823c678b2bb..f5c1b7114d9c 100644 --- a/test/functional/apps/management/_index_pattern_results_sort.js +++ b/test/functional/apps/management/_index_pattern_results_sort.js @@ -39,6 +39,12 @@ export default function ({ getService, getPageObjects }) { before(async function () { // delete .kibana index and then wait for OpenSearch Dashboards to re-create it await opensearchDashboardsServer.uiSettings.replace({}); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.createIndexPattern(); + }); + + after(async function () { + return await PageObjects.settings.removeIndexPattern(); }); const columns = [ @@ -64,14 +70,6 @@ export default function ({ getService, getPageObjects }) { columns.forEach(function (col) { describe('sort by heading - ' + col.heading, function indexPatternCreation() { - before(async function () { - await PageObjects.settings.createIndexPattern(); - }); - - after(async function () { - return await PageObjects.settings.removeIndexPattern(); - }); - it('should sort ascending', async function () { await PageObjects.settings.sortBy(col.heading); const rowText = await col.selector(); @@ -85,17 +83,9 @@ export default function ({ getService, getPageObjects }) { }); }); }); + describe('field list pagination', function () { const EXPECTED_FIELD_COUNT = 86; - - before(async function () { - await PageObjects.settings.createIndexPattern(); - }); - - after(async function () { - return await PageObjects.settings.removeIndexPattern(); - }); - it('makelogs data should have expected number of fields', async function () { await retry.try(async function () { const TabCount = await PageObjects.settings.getFieldsTabCount(); diff --git a/test/functional/apps/management/_opensearch_dashboards_settings.js b/test/functional/apps/management/_opensearch_dashboards_settings.js index 98cda687e23b..0e310953e8a2 100644 --- a/test/functional/apps/management/_opensearch_dashboards_settings.js +++ b/test/functional/apps/management/_opensearch_dashboards_settings.js @@ -39,8 +39,8 @@ export default function ({ getService, getPageObjects }) { before(async function () { // delete .kibana index and then wait for OpenSearch Dashboards to re-create it await opensearchDashboardsServer.uiSettings.replace({}); - await PageObjects.settings.createIndexPattern('logstash-*'); await PageObjects.settings.navigateTo(); + await PageObjects.settings.createIndexPattern('logstash-*'); }); after(async function afterAll() { diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 7d9ae2be1166..7acceff07fce 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -161,6 +161,7 @@ export default function ({ getService, getPageObjects }) { const fromTime = 'Sep 17, 2015 @ 06:31:44.000'; const toTime = 'Sep 18, 2015 @ 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('logstash-*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); @@ -169,31 +170,31 @@ export default function ({ getService, getPageObjects }) { }); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\n18'); - }); + const rowData = await PageObjects.discover.getDataGridTableValues(); + expect(rowData[0][0]).to.be('Sep 18, 2015 @ 18:20:57.916'); + expect(rowData[0][1]).to.be('18'); }); //add a test to sort numeric scripted field it('should sort scripted field value in Discover', async function () { - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName}`); - // after the first click on the scripted field, it becomes secondary sort after time. - // click on the timestamp twice to make it be the secondary sort key. - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click(`dataGridHeaderCell-${scriptedPainlessFieldName}`); + await PageObjects.discover.clickTableHeaderListItem(scriptedPainlessFieldName, 'Sort A-Z'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 17, 2015 @ 10:53:14.181\n-1'); - }); - - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName}`); + await testSubjects.click('dataGridHeaderCell-@timestamp'); + await PageObjects.discover.clickTableHeaderListItem('@timestamp', 'Sort A-Z'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 17, 2015 @ 06:32:29.479\n20'); - }); + const sortedDataByTimeField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByTimeField[0][0]).contain('Sep 17, 2015 @ 10:53:14.181'); + expect(sortedDataByTimeField[0][1]).contain('-1'); + + // click the column sorting button to remove painless field sort + // should sort only by time field + await testSubjects.click('dataGridColumnSortingButton'); + await PageObjects.discover.removeSort(`${scriptedPainlessFieldName}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + const sortedDataByPainlessField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByPainlessField[0][0]).contain('Sep 17, 2015 @ 06:32:29.479'); + expect(sortedDataByPainlessField[0][1]).contain('20'); }); it('should filter by scripted field value in Discover', async function () { @@ -278,6 +279,7 @@ export default function ({ getService, getPageObjects }) { const fromTime = 'Sep 17, 2015 @ 06:31:44.000'; const toTime = 'Sep 18, 2015 @ 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('logstash-*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); @@ -286,31 +288,31 @@ export default function ({ getService, getPageObjects }) { }); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\ngood'); - }); + const rowData = await PageObjects.discover.getDataGridTableValues(); + expect(rowData[0][0]).to.be('Sep 18, 2015 @ 18:20:57.916'); + expect(rowData[0][1]).to.be('good'); }); //add a test to sort string scripted field it('should sort scripted field value in Discover', async function () { - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); - // after the first click on the scripted field, it becomes secondary sort after time. - // click on the timestamp twice to make it be the secondary sort key. - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click(`dataGridHeaderCell-${scriptedPainlessFieldName2}`); + await PageObjects.discover.clickTableHeaderListItem(scriptedPainlessFieldName2, 'Sort A-Z'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 17, 2015 @ 09:48:40.594\nbad'); - }); - - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + await testSubjects.click('dataGridHeaderCell-@timestamp'); + await PageObjects.discover.clickTableHeaderListItem('@timestamp', 'Sort A-Z'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 17, 2015 @ 06:32:29.479\ngood'); - }); + const sortedDataByTimeField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByTimeField[0][0]).contain('Sep 17, 2015 @ 09:48:40.594'); + expect(sortedDataByTimeField[0][1]).contain('bad'); + + // click the column sorting button to remove painless field sort + // should sort only by time field + await testSubjects.click('dataGridColumnSortingButton'); + await PageObjects.discover.removeSort(`${scriptedPainlessFieldName2}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + const sortedDataByPainlessField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByPainlessField[0][0]).contain('Sep 17, 2015 @ 06:32:29.479'); + expect(sortedDataByPainlessField[0][1]).contain('good'); }); it('should filter by scripted field value in Discover', async function () { @@ -373,6 +375,7 @@ export default function ({ getService, getPageObjects }) { const fromTime = 'Sep 17, 2015 @ 06:31:44.000'; const toTime = 'Sep 18, 2015 @ 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('logstash-*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); @@ -381,10 +384,33 @@ export default function ({ getService, getPageObjects }) { }); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\ntrue'); - }); + const rowData = await PageObjects.discover.getDataGridTableValues(); + expect(rowData[0][0]).to.be('Sep 18, 2015 @ 18:20:57.916'); + expect(rowData[0][1]).to.be('true'); + }); + + // existing bug: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5126 hence the issue is skipped + // TODO: replace updateExpectedResultHere with actual data value once bug is fixed + it.skip('should sort scripted field value in Discover', async function () { + await testSubjects.click(`dataGridHeaderCell-${scriptedPainlessFieldName2}`); + await PageObjects.discover.clickTableHeaderListItem( + scriptedPainlessFieldName2, + 'Sort True-False' + ); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.click('dataGridHeaderCell-@timestamp'); + await PageObjects.discover.clickTableHeaderListItem('@timestamp', 'Sort A-Z'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const sortedDataByTimeField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByTimeField[0][0]).contain('updateExpectedResultHere'); + expect(sortedDataByTimeField[0][1]).contain('true'); + + await testSubjects.click('dataGridColumnSortingButton'); + await PageObjects.discover.removeSort(`${scriptedPainlessFieldName2}`); + await PageObjects.header.waitUntilLoadingHasFinished(); + const sortedDataByPainlessField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByPainlessField[0][0]).contain('updateExpectedResultHere'); + expect(sortedDataByPainlessField[0][1]).contain('false'); }); it('should filter by scripted field value in Discover', async function () { @@ -399,28 +425,6 @@ export default function ({ getService, getPageObjects }) { await filterBar.removeAllFilters(); }); - //add a test to sort boolean - //existing bug: https://github.com/elastic/kibana/issues/75519 hence the issue is skipped. - it.skip('should sort scripted field value in Discover', async function () { - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); - // after the first click on the scripted field, it becomes secondary sort after time. - // click on the timestamp twice to make it be the secondary sort key. - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('updateExpectedResultHere\ntrue'); - }); - - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('updateExpectedResultHere\nfalse'); - }); - }); - it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -469,6 +473,7 @@ export default function ({ getService, getPageObjects }) { const fromTime = 'Sep 17, 2015 @ 19:22:00.000'; const toTime = 'Sep 18, 2015 @ 07:00:00.000'; await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('logstash-*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); @@ -477,32 +482,30 @@ export default function ({ getService, getPageObjects }) { }); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('Sep 18, 2015 @ 06:52:55.953\n2015-09-18 07:00'); - }); + const rowData = await PageObjects.discover.getDataGridTableValues(); + expect(rowData[0][0]).to.be('Sep 18, 2015 @ 06:52:55.953'); + expect(rowData[0][1]).to.be('2015-09-18 07:00'); }); - //add a test to sort date scripted field - //https://github.com/elastic/kibana/issues/75711 + // existing bug: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5127 hence the issue is skipped + // TODO: replace updateExpectedResultHere with actual data value once bug is fixed it.skip('should sort scripted field value in Discover', async function () { - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); - // after the first click on the scripted field, it becomes secondary sort after time. - // click on the timestamp twice to make it be the secondary sort key. - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); - await testSubjects.click('docTableHeaderFieldSort_@timestamp'); + await testSubjects.click(`dataGridHeaderCell-${scriptedPainlessFieldName2}`); + await PageObjects.discover.clickTableHeaderListItem(scriptedPainlessFieldName2, 'Sort A-Z'); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('updateExpectedResultHere\n2015-09-18 07:00'); - }); + await testSubjects.click('dataGridHeaderCell-@timestamp'); + await PageObjects.discover.clickTableHeaderListItem('@timestamp', 'Sort A-Z'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const sortedDataByTimeField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByTimeField[0][0]).contain('updateExpectedResultHere'); + expect(sortedDataByTimeField[0][1]).contain('2015-09-18 07:00'); - await testSubjects.click(`docTableHeaderFieldSort_${scriptedPainlessFieldName2}`); + await testSubjects.click('dataGridColumnSortingButton'); + await PageObjects.discover.removeSort(`${scriptedPainlessFieldName2}`); await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.be('updateExpectedResultHere\n2015-09-18 07:00'); - }); + const sortedDataByPainlessField = await PageObjects.discover.getDataGridTableValues(); + expect(sortedDataByPainlessField[0][0]).contain('updateExpectedResultHere'); + expect(sortedDataByPainlessField[0][1]).contain('2015-09-18 07:00'); }); it('should filter by scripted field value in Discover', async function () { diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js index adeefa118345..304f757d006a 100644 --- a/test/functional/apps/management/_scripted_fields_preview.js +++ b/test/functional/apps/management/_scripted_fields_preview.js @@ -38,9 +38,8 @@ export default function ({ getService, getPageObjects }) { describe('scripted fields preview', () => { before(async function () { await browser.setWindowSize(1200, 800); - await PageObjects.settings.createIndexPattern(); - await PageObjects.settings.navigateTo(); + await PageObjects.settings.createIndexPattern(); await PageObjects.settings.clickOpenSearchDashboardsIndexPatterns(); await PageObjects.settings.clickIndexPatternLogstash(); await PageObjects.settings.clickScriptedFieldsTab(); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index f09be976e38e..f81c287a478f 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -206,6 +206,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider } public async getDocHeader() { + const table = this.dataGrid; const docHeader = await find.byCssSelector('thead > tr:nth-child(1)'); return await docHeader.getVisibleText(); } @@ -439,6 +440,82 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider public async clearSavedQuery() { await testSubjects.click('saved-query-management-clear-button'); } + + /** + * Retrieves data grid table values. + * + * This function fetches the values present in a data grid table. + * + * @returns {Promise} A promise resolving to the table values. + */ + public async getDataGridTableValues(): Promise { + return await dataGridTable.getDataGridTableValues(); + } + + /** + * Removes sorting from a specified column in a data grid. + * + * This function removes sorting of column if applied in the sort field. + * + * @param {string} columnName - The name of the column from which sorting should be removed. + * @returns {Promise} + */ + public async removeSort(columnName: string): Promise { + const parentDiv = await testSubjects.find( + `euiDataGridColumnSorting-sortColumn-${columnName}` + ); + + // Within this parent div, locate the button with the specified aria-label using CSS and click it + const cssSelector = `.euiDataGridColumnSorting__button[aria-label^="Remove from data grid sort: ${columnName}"]`; + const buttonToRemoveSort = await parentDiv.findByCssSelector(cssSelector); + await buttonToRemoveSort.click(); + } + + /** + * Clicks on a list item within the table column header based on column name and title. + * + * This function searches for a list item via title associated with a given column name. + * Once the item is found, its associated button is clicked. + * + * @param {string} columnName - The name of the column. + * @param {string} title - The title of the list item to be clicked. + * @returns {Promise} + * @throws Will throw an error if a clickable list item with the specified title is not found. + */ + public async clickTableHeaderListItem(columnName: string, title: string): Promise { + // locate the ul using the columnName + const ulElement = await testSubjects.find(`dataGridHeaderCellActionGroup-${columnName}`); + const $ = await ulElement.parseDomContent(); + + // loop through each
  • within the ul + const liElements = $('li').toArray(); + let index = 0; + + for (const liElement of liElements) { + const li = $(liElement); + + // Check if the li contains the isClickable class substring + if (li.is('li[class*="euiListGroupItem-isClickable"]')) { + const span = li.find(`span[title="${title}"]`); + + // If the span with the given title is found + if (span.length > 0) { + // find and click the button + const seleniumLiElement = await ulElement.findByCssSelector( + `li:nth-child(${index + 1}) button` + ); + // Click on the located WebElement + await seleniumLiElement.click(); + return; + } + } + index++; + } + + throw new Error( + `Could not find a clickable list item for column "${columnName}" with list item "${title}".` + ); + } } return new DiscoverPage(); diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 247b74948168..b23eabb4ef01 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -71,6 +71,34 @@ export function DataGridProvider({ getService }: FtrProviderContext) { }; } + /** + * Retrieves the values from a data grid table. + * + * The function fetches values present in a data grid table and organizes them into rows and columns. + * Each row is an array of strings, and the entire table is an array of such rows. + * + * @returns {Promise} A promise resolving to a 2D array of table values. + */ + async getDataGridTableValues(): Promise { + const table = await testSubjects.find('docTable'); + const $ = await table.parseDomContent(); + const cellsArr = $.findTestSubjects('dataGridRowCell').toArray(); + const rows: string[][] = []; + let rowIdx = -1; + + for (const cell of cellsArr) { + const cCell = $(cell); + const isFirstColumn = cCell.attr('class').includes('euiDataGridRowCell--firstColumn'); + if (isFirstColumn) { + rowIdx++; + rows[rowIdx] = []; + } else { + rows[rowIdx].push(this.getTextFromCell(cCell)); + } + } + return Promise.resolve(rows); + } + /** * Retrieves the header fields of the data grid. * @@ -100,6 +128,15 @@ export function DataGridProvider({ getService }: FtrProviderContext) { await find.clickByButtonText('Remove column'); } + /** + * Retrieves values from a specific column in a data grid table. + * + * This function targets a column based on a CSS class selector and retrieves its cell values. + * It makes use of the Cheerio library to parse and navigate the DOM. + * + * @param {string} selector - The CSS class suffix used to identify cells of the desired column. + * @returns {Promise} A promise resolving to an array of cell values from the specified column. + */ async getDataGridTableColumn(selector: string): Promise { const table = await find.byCssSelector('.euiDataGrid'); const $ = await table.parseDomContent(); @@ -111,12 +148,26 @@ export function DataGridProvider({ getService }: FtrProviderContext) { const cCell = $(cell); if (cCell.hasClass(`euiDataGridRowCell--${selector}`)) { // The column structure is very nested to get the actual text - columnValues.push(cCell.children().children().children().children().text()); + columnValues.push(this.getTextFromCell(cCell)); } }); return columnValues; } + + /** + * Extracts the text from a cell in the data grid. + * + * Given a cell represented by a Cheerio object, this function navigates its nested structure + * to extract the contained text. + * + * @param {any} cCell - The Cheerio representation of the cell from which text needs to be extracted. + * @returns {string} The extracted text from the cell. + */ + getTextFromCell(cCell: any): string { + // navigate the nested structure and get the text + return cCell.children().children().children().children().text(); + } } return new DataGrid();