Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add e2e tests for superset embedded dashboards feature #3197

Merged
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
2fa3897
chore: add excludeByVersionTag to cypress config and add tagify depen…
HendrikThePendric Jan 28, 2025
a090c25
chore: allow mixing cucumber and non-cucumber specs
HendrikThePendric Jan 29, 2025
6cd75a6
chore: temporarily disable all cypress-cucumber stuff
HendrikThePendric Jan 29, 2025
ff01cf6
test: add cypress test for creating a superset embedded dashboard
HendrikThePendric Jan 29, 2025
675218b
chore: update dependencies so that cypress-tags could run
HendrikThePendric Jan 29, 2025
daf813f
chore: disable exclude by version tags and enable cucumber preprocessor
HendrikThePendric Jan 29, 2025
ae0f90e
chore: detect version during run and skip tests if below 42
HendrikThePendric Jan 29, 2025
73135fc
chore: keep cucumber specs separate from js specs to prevent
HendrikThePendric Jan 29, 2025
0cd1fe1
fix: close more-menu after toggling dashboard starred state
HendrikThePendric Jan 30, 2025
94668c4
fix: ensure dashboard iframe is reloaded when dashboard embed data is…
HendrikThePendric Jan 30, 2025
daee5c7
test: finalize e2e test suite for superset embedded dashboard feature
HendrikThePendric Jan 30, 2025
f9ccfdd
chore: ensure the before callback has a this scope
HendrikThePendric Jan 30, 2025
15de3d4
chore: update yarn.lock
HendrikThePendric Jan 31, 2025
a24953d
chore: upgrade node version in all workflows
HendrikThePendric Jan 31, 2025
cf4517f
chore: adjust cypress spec path
HendrikThePendric Jan 31, 2025
0ac529e
chore: adjust test matrix so it includes both cucumber and regular cy…
HendrikThePendric Jan 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cypress-cucumber-preprocessorrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"nonGlobalStepDefinitions": true,
"e2e": {
"stepDefinitions": ["cypress/e2e/**/*.{js,ts}"]
"stepDefinitions": ["cypress/e2e_cucumber/**/*.{js,ts}"]
}
}
6 changes: 5 additions & 1 deletion cypress.config.js
Original file line number Diff line number Diff line change
@@ -5,10 +5,14 @@ const createEsbuildPlugin = require('@badeball/cypress-cucumber-preprocessor/esb
const createBundler = require('@bahmutov/cypress-esbuild-preprocessor')
const { chromeAllowXSiteCookies } = require('@dhis2/cypress-plugins')
const { defineConfig } = require('cypress')
// const {
// excludeByVersionTags,
// } = require('./cypress/plugins/excludeByVersionTags.js')

async function setupNodeEvents(on, config) {
await addCucumberPreprocessorPlugin(on, config)
chromeAllowXSiteCookies(on, config)
// excludeByVersionTags(on, config)

on(
'file:preprocessor',
@@ -26,7 +30,7 @@ module.exports = defineConfig({
e2e: {
setupNodeEvents,
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/*.feature',
specPattern: ['cypress/e2e_cucumber/*.feature', 'cypress/e2e/*.cy.js'],
viewportWidth: 1280,
viewportHeight: 800,
defaultCommandTimeout: 45000,
210 changes: 210 additions & 0 deletions cypress/e2e/embedded_superset_dashboard.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { newButtonSel } from '../elements/viewDashboard.js'
import { EXTENDED_TIMEOUT } from '../support/utils.js'

const SUPERSET_BASE_URL = 'https://superset-test.dhis2.org'
const NAME = 'My new dashboard'
const NAME_UPDATED = 'My updated dashboard'
const CODE = 'MY_CODE'
const DESCRIPTION = 'My dashboard description text'
const DESCRIPTION_UPATED = 'My updated dashboard description'
const UUID = '2e5ae28f-60d1-4fb9-a609-5bd4586bf4ac'
const UUID_UPDATED = '418b4581-3c2a-43a9-8561-9725eadcaffd'
const SUPERSET_DASHBOARD_STUB = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Superset dashboard stub</title>
</head>
<body>
<h1>Superset dashboard stub</h1>
</body>
</html>
`

const getInputByLabelText = (labelText, inputTag = 'input') =>
cy.get('form').contains('label', labelText).parent().find(inputTag)

describe('Creating, viewing, editing and deleting an embedded superset dashboard', function () {
before(function () {
// Skip this test if the DHIS2 Core version is below 42
const version = parseInt(Cypress.env('dhis2InstanceVersion'))
if (version < 42) {
this.skip()
}
})

beforeEach(() => {
// Fake support for embedded dashboards by intercepting the requests below
cy.intercept('**', (req) => {
if (req.url.includes('/systemSettings?')) {
// Append system setting for embedded dashboard support
req.continue((resp) => {
resp.body.keyEmbeddedDashboardsEnabled = true
return resp
})
} else if (req.url.includes('/superset-gateway/api/info')) {
// Stub the response to the superset gateway info request
req.reply({
supersetBaseUrl: SUPERSET_BASE_URL,
apiDocsPath:
'/superset-gateway/apidocs/dhis2-superset-gateway/swagger-ui/',
})
} else if (
req.url.includes(
'/superset-gateway/api/guestTokens/dhis2/dashboards/'
)
) {
// Stub the response to the superset gatewat guest token request
req.reply({
// Note that the string below does need to have a particular pattern: it was
// copied from the network tab and cannot be replaced by a random shorter string.
// The superset embed SDK must do some sort of pattern validation on it
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoiYW5hbHl0aWNzIiwiZmlyc3RfbmFtZSI6IkRISVMgMiBTdXBlcnNldCIsImxhc3RfbmFtZSI6IkdhdGV3YXkifSwicmVzb3VyY2VzIjpbeyJ0eXBlIjoiZGFzaGJvYXJkIiwiaWQiOiI2NmEzNGMyYS1mYTA2LTRkYjUtYmQ2ZS0wOGZkMjRiZjVhY2MifV0sInJsc19ydWxlcyI6W10sImlhdCI6MTczNzk5NTE0My4yMTU5NjcsImV4cCI6MTczNzk5NTQ0My4yMTU5NjcsImF1ZCI6Imh0dHA6Ly8wLjAuMC4wOjgwODAvIiwidHlwZSI6Imd1ZXN0In0.AayCHirBjomllKxThOCQsn4RHoIfaXfULOVtcnylhj8',
})
} else if (req.url.includes(`${SUPERSET_BASE_URL}/embedded/`)) {
req.reply(SUPERSET_DASHBOARD_STUB)
} else {
// Just return the response by default
req.continue((resp) => resp)
}
})
})

it('creates an embedded superset dashboard', () => {
// Start a new dashboard from the start page
cy.visit('#/start')

cy.get(newButtonSel, EXTENDED_TIMEOUT).click()

// Choose the embedded dashboard option
cy.contains('External: Data from another source')
.should('be.visible')
.click()

// Click the configure source button
cy.contains('Configure source').should('be.visible').click()

// A modal form to create a new embedded dashboard is showing
cy.contains(
'New dashboard: configure external source (superset)'
).should('be.visible')

// Check all initial values and change them
getInputByLabelText('Title').should('have.value', '').type(NAME)
getInputByLabelText('Code').should('have.value', '').type(CODE)
getInputByLabelText('Description', 'textarea')
.should('have.value', '')
.type(DESCRIPTION)
getInputByLabelText('Superset Embed ID')
.should('have.value', '')
.type(UUID)
getInputByLabelText('Show chart controls on dashboard items')
.should('be.checked')
.uncheck()
getInputByLabelText('Show filters').should('be.checked').uncheck()

// Click the create button
cy.contains('Save dashboard').should('be.enabled').click()

cy.contains('h3', NAME).should('be.visible')
cy.contains('External data').should('be.visible')
// An iframe should be visible with the UUID in the src
cy.get('iframe')
.should('be.visible')
.and('have.attr', 'src')
.and('contain', UUID)
})

it('has some options disabled in the action-bar', () => {
// Primary actions
cy.contains('button', 'Slideshow').should('be.disabled')
cy.contains('button', 'Filter').should('be.disabled')
// Actions in the more-menu
cy.getByDataTest('more-actions-button').should('be.enabled').click()
cy.contains('a', 'Make available offline').should(
'have.attr',
'aria-disabled',
'true'
)
cy.contains('a', 'Print')
.should('have.attr', 'aria-disabled', 'true')
.and('have.attr', 'aria-expanded', 'false')
// Close the menu by clicking the backdrop
cy.get('.backdrop').should('be.visible').click()
})

it('shows the description', () => {
cy.getByDataTest('more-actions-button').should('be.enabled').click()
cy.contains('a', 'Show description').should('be.visible').click()
cy.contains(DESCRIPTION).should('be.visible')
// Keep visible so we can check it updates correctly later on
})

it('stars and unstars the superset embedded dashboard', () => {
// Can be starred via menu bar
cy.getByDataTest('dashboard-unstarred').click()
cy.getByDataTest('dashboard-starred').should('be.visible')
// And unstarred via ...menu
cy.getByDataTest('more-actions-button').should('be.enabled').click()
cy.contains('a', 'Unstar dashboard').should('be.visible').click()
cy.getByDataTest('dashboard-unstarred').should('be.visible')
})

it('can share the superset embedded dashboard', () => {
cy.contains('button', 'Share').should('be.enabled').click()
cy.contains('h1', `Sharing and access: ${NAME}`).should('be.visible')
// We don't test the actual sharing, just if the sharing modal pops up
cy.contains('button', 'Close').should('be.enabled').click()
})

it('edits the superset embedded dashboard', () => {
cy.contains('button', 'Edit').should('be.enabled').click()
cy.contains('Edit external dashboard').should('be.visible')
// Check all initial values are as when created and change some of them
getInputByLabelText('Title')
.should('have.value', NAME)
.clear()
.type(NAME_UPDATED)
getInputByLabelText('Code').should('have.value', CODE)
getInputByLabelText('Description', 'textarea')
.should('have.value', DESCRIPTION)
.clear()
.type(DESCRIPTION_UPATED)
getInputByLabelText('Superset Embed ID')
.should('have.value', UUID)
.clear()
.type(UUID_UPDATED)
getInputByLabelText('Show chart controls on dashboard items').should(
'not.be.checked'
)
getInputByLabelText('Show filters').should('not.be.checked')

// Click the update button
cy.contains('Update dashboard').should('be.enabled').click()

cy.contains('h3', NAME_UPDATED).should('be.visible')
cy.contains('External data').should('be.visible')
cy.contains(DESCRIPTION_UPATED).should('be.visible')
// An iframe should be visible with the UUID in the src
cy.get('iframe')
.should('be.visible')
.and('have.attr', 'src')
.and('contain', UUID_UPDATED)
})

it('hides the description', () => {
cy.getByDataTest('more-actions-button').should('be.enabled').click()
cy.contains('a', 'Hide description').should('be.visible').click()
cy.contains(DESCRIPTION_UPATED).should('not.exist')
})

it('deletes the new superset embedded dashboard', () => {
cy.contains('Edit').should('be.enabled').click()
cy.contains('Edit external dashboard').should('be.visible')
cy.contains('Delete').should('be.enabled').click()
cy.contains('Delete dashboard').should('be.visible')
cy.contains('button', 'Delete').should('be.enabled').click()
cy.url().should('satisfy', (href) => href.endsWith('/#/'))
})
})
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -42,7 +42,8 @@
"cy:run": "start-server-and-test 'yarn cy:start' http://localhost:3000 'yarn cypress run --browser chrome headless'"
},
"devDependencies": {
"@badeball/cypress-cucumber-preprocessor": "^20.1.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@badeball/cypress-cucumber-preprocessor": "^22.0.0",
"@bahmutov/cypress-esbuild-preprocessor": "^2.2.1",
"@cypress/webpack-preprocessor": "^6.0.2",
"@dhis2/cli-app-scripts": "^11.7.1",
@@ -56,6 +57,7 @@
"@testing-library/react": "^12",
"cypress": "^13.13.1",
"cypress-real-events": "^1.13.0",
"cypress-tags": "^1.2.2",
"d2-manifest": "^1.0.0",
"eslint-plugin-cypress": "^3.3.0",
"immutability-helper": "^3.1.1",
@@ -65,7 +67,8 @@
"redux-mock-store": "^1.5.4",
"resize-observer-polyfill": "^1.5.1",
"semantic-release": "^20",
"start-server-and-test": "^1.14.0"
"start-server-and-test": "^1.14.0",
"typescript": "^5.7.3"
},
"jest": {
"moduleNameMapper": {
5 changes: 4 additions & 1 deletion src/components/DashboardsBar/InformationBlock/ActionsBar.js
Original file line number Diff line number Diff line change
@@ -161,7 +161,10 @@ const ActionsBar = ({
? i18n.t('Unstar dashboard')
: i18n.t('Star dashboard')
}
onClick={toggleDashboardStarred}
onClick={() => {
toggleDashboardStarred()
setMoreOptionsIsOpen(false)
}}
/>
<MenuItem
dense
4 changes: 4 additions & 0 deletions src/pages/view/EmbeddedSupersetDashboard.js
Original file line number Diff line number Diff line change
@@ -70,6 +70,10 @@ export const EmbeddedSupersetDashboard = () => {
loadEmbeddedSupersetDashboard()
}, [loading, error, success, loadEmbeddedSupersetDashboard])

useEffect(() => {
dispatch({ type: LOAD_RESET })
}, [embedData])

return (
<div className={styles.container}>
<div
Loading
Loading