Skip to content

Commit

Permalink
chore: initial e2e tests (#285)
Browse files Browse the repository at this point in the history
* chore: initial e2e tests

* chore: run playwright on CI
  • Loading branch information
tracy-french authored Mar 6, 2024
1 parent d4695d6 commit 673e0ac
Show file tree
Hide file tree
Showing 11 changed files with 616 additions and 33 deletions.
78 changes: 78 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: E2E tests
on:
pull_request:

permissions:
contents: read
id-token: write

jobs:
resolve-versions:
name: Resolve Grafana images
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
matrix: ${{ steps.resolve-versions.outputs.matrix }}
steps:
- name: Resolve Grafana E2E versions
id: resolve-versions
uses: grafana/plugin-actions/e2e-version@main
with:
version-resolver-type: version-support-policy

playwright-tests:
needs: resolve-versions
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
GRAFANA_IMAGE: ${{fromJson(needs.resolve-versions.outputs.matrix)}}
name: e2e ${{ matrix.GRAFANA_IMAGE.name }}@${{ matrix.GRAFANA_IMAGE.VERSION }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
cache: 'yarn'
node-version-file: .nvmrc

- name: Install Mage
uses: magefile/mage-action@v3
with:
install-only: true

- name: Install yarn dependencies
run: yarn install

- name: Build binaries
run: mage -v build:linux

- name: Build frontend
run: yarn build

- name: Install Playwright Browsers
run: yarn playwright install --with-deps

- name: Start Grafana
run: |
docker-compose pull
GRAFANA_VERSION=${{ matrix.GRAFANA_IMAGE.VERSION }} GRAFANA_IMAGE=${{ matrix.GRAFANA_IMAGE.NAME }} docker-compose up -d
- name: Wait for Grafana to start
uses: nev7n/wait_for_response@v1
with:
url: 'http://localhost:3000/'
responseCode: 200
timeout: 60000
interval: 500

- name: Run Playwright tests
id: run-tests
run: yarn playwright test

- name: Publish report to GCS
if: ${{ (always() && steps.run-tests.outcome == 'success') || (failure() && steps.run-tests.outcome == 'failure') && github.event.organization.login == 'grafana' }}
uses: grafana/plugin-actions/publish-report@main
with:
grafana-version: ${{ matrix.GRAFANA_IMAGE.VERSION }}
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ dist/
# until we can figure out how to install the mockery executable on circleci, leave this commented out
#**/mocks/*.*
__debug_bin
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/

# ignore all provisioning files except for e2e placeholders.
provisioning/datasources/*
!provisioning/datasources/*.e2e.yaml
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ version: '3.0'
services:
grafana:
container_name: 'grafana-iot-sitewise-datasource'
platform: 'linux/amd64'
build:
context: ./.config
args:
grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise}
grafana_version: ${GRAFANA_VERSION:-9.2.5}
ports:
- 3000:3000/tcp
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
"@grafana/e2e": "10.2.0",
"@grafana/e2e-selectors": "10.2.0",
"@grafana/eslint-config": "^6.0.0",
"@grafana/plugin-e2e": "^0.18.0",
"@grafana/runtime": "10.2.0",
"@grafana/tsconfig": "^1.2.0-rc1",
"@grafana/ui": "10.2.0",
"@playwright/test": "^1.42.0",
"@swc/core": "1.3.90",
"@swc/helpers": "0.5.0",
"@swc/jest": "0.2.26",
Expand Down
41 changes: 41 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { dirname } from 'path';
import { defineConfig, devices } from '@playwright/test';

const pluginE2eAuth = `${dirname(require.resolve('@grafana/plugin-e2e'))}/auth`;

/**
* See https://playwright.dev/docs/test-configuration.
* See https://grafana.com/developers/plugin-tools/e2e-test-a-plugin/introduction
*/
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
timeout: 60000,
expect: {
timeout: 30000,
},
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},

projects: [
{
name: 'auth',
testDir: pluginE2eAuth,
testMatch: [/.*\.js/],
},
{
name: 'run-tests',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/admin.json',
},
dependencies: ['auth'],
},
],
});
11 changes: 11 additions & 0 deletions provisioning/datasources/mock-iot-sitewise.e2e.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: 1

deleteDatasources:
- name: E2E Mock IoT SiteWise
orgId: 1

datasources:
- name: E2E Mock IoT SiteWise
type: grafana-iot-sitewise-datasource
uid: e2e-mock-iot-sitewise
version: 1
4 changes: 4 additions & 0 deletions src/components/query/PropertyQueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ export class PropertyQueryEditor extends PureComponent<Props, State> {
<EditorField label="Property Alias" tooltip={queryTooltip} tooltipInteractive htmlFor="alias" width={80}>
<Input
id="alias"
aria-label="Property alias"
value={query.propertyAlias}
onChange={this.onAliasChange}
placeholder="optional alias that identifies the property, such as an OPC-UA server data stream path"
Expand Down Expand Up @@ -502,6 +503,7 @@ export class PropertyQueryEditor extends PureComponent<Props, State> {
interactive
>
<Input
aria-label="Property alias"
value={query.propertyAlias}
onChange={this.onAliasChange}
placeholder="optional alias that identifies the property, such as an OPC-UA server data stream path"
Expand All @@ -514,6 +516,7 @@ export class PropertyQueryEditor extends PureComponent<Props, State> {
<div className="gf-form">
<InlineField label="Asset" labelWidth={firstLabelWith} grow={true}>
<Select
aria-label="Asset"
isMulti={true}
key={query.region ? query.region : 'default'}
isLoading={loading}
Expand Down Expand Up @@ -542,6 +545,7 @@ export class PropertyQueryEditor extends PureComponent<Props, State> {
<div className="gf-form">
<InlineField label="Property" labelWidth={firstLabelWith} grow={true}>
<Select
aria-label="Property"
isLoading={loading}
options={assetPropertyOptions}
value={currentAssetPropertyOption ?? null}
Expand Down
12 changes: 8 additions & 4 deletions src/components/query/QualityAndOrderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class QualityAndOrderRow extends PureComponent<Props> {
<EditorField label="Quality" width={15} htmlFor="quality">
<Select
id="quality"
aria-label="quality"
aria-label="Quality"
options={qualities}
value={qualities.find((v) => v.value === query.quality) ?? qualities[0]}
onChange={this.onQualityChange}
Expand All @@ -114,10 +114,10 @@ export class QualityAndOrderRow extends PureComponent<Props> {
menuPlacement="bottom"
/>
</EditorField>
<EditorField label="Format" width={10} htmlFor="time">
<EditorField label="Format" width={10} htmlFor="format">
<Select
id="time"
aria-label="Time"
id="format"
aria-label="Format"
value={query.responseFormat || SiteWiseResponseFormat.Table}
onChange={this.onResponseFormatChange}
options={FORMAT_OPTIONS}
Expand All @@ -141,6 +141,7 @@ export class QualityAndOrderRow extends PureComponent<Props> {
<div className="gf-form">
<InlineField label="Quality" labelWidth={firstLabelWith}>
<Select
aria-label="Quality"
width={20}
options={qualities}
value={qualities.find((v) => v.value === query.quality) ?? qualities[0]}
Expand All @@ -151,6 +152,7 @@ export class QualityAndOrderRow extends PureComponent<Props> {
</InlineField>
<InlineField label="Time" labelWidth={8}>
<Select
aria-label="Time"
options={ordering}
value={ordering.find((v) => v.value === query.timeOrdering) ?? ordering[0]}
onChange={this.onOrderChange}
Expand All @@ -161,6 +163,7 @@ export class QualityAndOrderRow extends PureComponent<Props> {

<InlineField label="Format" labelWidth={8}>
<Select
aria-label="Format"
value={query.responseFormat || SiteWiseResponseFormat.Table}
onChange={this.onResponseFormatChange}
options={FORMAT_OPTIONS}
Expand All @@ -170,6 +173,7 @@ export class QualityAndOrderRow extends PureComponent<Props> {
{isAssetPropertyInterpolatedQuery(query) && (
<InlineField label="Resolution" labelWidth={10}>
<Select
aria-label="Resolution"
width={18}
options={interpolatedResolutions}
value={interpolatedResolutions.find((v) => v.value === query.resolution) || interpolatedResolutions[0]}
Expand Down
67 changes: 39 additions & 28 deletions src/components/query/QueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,19 @@ export function QueryEditor(props: Props) {
case QueryType.ListAssetModels:
return null; // nothing required
case QueryType.ListAssets:
return <ListAssetsQueryEditor {...props} query={query as ListAssetsQuery} newFormStylingEnabled={newFormStylingEnabled} />;
return (
<ListAssetsQueryEditor
{...props}
query={query as ListAssetsQuery}
newFormStylingEnabled={newFormStylingEnabled}
/>
);
case QueryType.ListAssociatedAssets:
case QueryType.PropertyValue:
case QueryType.PropertyInterpolated:
case QueryType.PropertyAggregate:
case QueryType.PropertyValueHistory:
return <PropertyQueryEditor {...props} newFormStylingEnabled={newFormStylingEnabled} />;
return <PropertyQueryEditor {...props} newFormStylingEnabled={newFormStylingEnabled} />;
}
return <div>Missing UI for query type: {query.queryType}</div>;
};
Expand All @@ -84,6 +90,7 @@ export function QueryEditor(props: Props) {
<EditorFieldGroup>
<EditorField label="Query type" tooltip={queryTooltip} tooltipInteractive width={30}>
<Select
aria-label="Query type"
options={siteWiseQueryTypes}
value={currentQueryType}
onChange={onQueryTypeChange}
Expand All @@ -107,32 +114,36 @@ export function QueryEditor(props: Props) {
{renderQuery(query, true)}
</EditorRows>
</>
) :<>
<div className="gf-form">
<InlineField label="Query type" labelWidth={firstLabelWith} grow={true} tooltip={queryTooltip} interactive>
<Select
options={siteWiseQueryTypes}
value={currentQueryType}
onChange={onQueryTypeChange}
placeholder="Select query type"
menuPlacement="bottom"
/>
</InlineField>
<InlineField label="Region" labelWidth={14}>
<Select
width={18}
options={regions}
value={standardRegionOptions.find((v) => v.value === query.region) || defaultRegion}
onChange={onRegionChange}
backspaceRemovesValue={true}
allowCustomValue={true}
isClearable={true}
menuPlacement="bottom"
/>
</InlineField>
</div>

{renderQuery(query)}</>}
) : (
<>
<div className="gf-form">
<InlineField label="Query type" labelWidth={firstLabelWith} grow={true} tooltip={queryTooltip} interactive>
<Select
aria-label="Query type"
options={siteWiseQueryTypes}
value={currentQueryType}
onChange={onQueryTypeChange}
placeholder="Select query type"
menuPlacement="bottom"
/>
</InlineField>
<InlineField label="Region" labelWidth={14}>
<Select
width={18}
options={regions}
value={standardRegionOptions.find((v) => v.value === query.region) || defaultRegion}
onChange={onRegionChange}
backspaceRemovesValue={true}
allowCustomValue={true}
isClearable={true}
menuPlacement="bottom"
/>
</InlineField>
</div>

{renderQuery(query)}
</>
)}
</>
);
}
Loading

0 comments on commit 673e0ac

Please sign in to comment.