mirror of https://github.com/grafana/grafana
commit
f5acaacfa9
@ -0,0 +1,67 @@ |
||||
name: Publish artifacts to bucket |
||||
on: |
||||
workflow_call: |
||||
inputs: |
||||
pattern: |
||||
description: | |
||||
(From actinos/download-artifact) Glob pattern of artifacts (instead of `name`) |
||||
Be careful when using this option; the contents of the root of each artifact are coalesced, so ensure that they do not collide. |
||||
type: string |
||||
required: false |
||||
name: |
||||
description: (From actinos/download-artifact) Name of the GitHub artifact to upload (Ignored if `pattern` is set) |
||||
type: string |
||||
required: false |
||||
bucket: |
||||
description: Name of the GCS bucket |
||||
type: string |
||||
required: true |
||||
bucket-path: |
||||
description: Path in the GCS bucket |
||||
type: string |
||||
required: false |
||||
default: "." |
||||
environment: |
||||
description: "'prod' or 'dev'" |
||||
type: string |
||||
required: false |
||||
default: dev |
||||
run-id: |
||||
type: string |
||||
required: true |
||||
service-account: |
||||
type: string |
||||
required: false |
||||
default: github-prerelease-writer@grafanalabs-workload-identity.iam.gserviceaccount.com |
||||
jobs: |
||||
publish: |
||||
runs-on: github-hosted-ubuntu-x64-small |
||||
name: Publish |
||||
permissions: |
||||
id-token: write |
||||
steps: |
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 |
||||
with: |
||||
name: ${{ inputs.name }} |
||||
pattern: ${{ inputs.pattern }} |
||||
run-id: ${{ inputs.run-id }} |
||||
path: ./artifact |
||||
- name: Log in to GCS |
||||
id: login-to-gcs |
||||
uses: grafana/shared-workflows/actions/login-to-gcs@login-to-gcs/v0.2.1 |
||||
with: |
||||
environment: ${{ inputs.environment }} |
||||
service_account: ${{ inputs.service-account }} |
||||
- name: Coalesce artifacts |
||||
run: | |
||||
mkdir out |
||||
find ./artifact -mindepth 2 -maxdepth 2 -exec cp -r {} out/ \; |
||||
ls -al out |
||||
- name: Upload artifacts |
||||
uses: grafana/shared-workflows/actions/push-to-gcs@push-to-gcs-v0.2.0 |
||||
with: |
||||
bucket: ${{ inputs.bucket }} |
||||
environment: ${{ inputs.environment }} |
||||
parent: false |
||||
path: out |
||||
bucket_path: ${{ inputs.bucket-path }} |
@ -1,140 +0,0 @@ |
||||
var dashboardSettings = [ |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=settings', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=annotations', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=templating', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=links', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=versions', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=permissions', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge?orgId=1&editview=dashboard_json', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
]; |
||||
var config = { |
||||
defaults: { |
||||
concurrency: 1, |
||||
runners: ['axe'], |
||||
useIncognitoBrowserContext: false, |
||||
standard: 'WCAG2AA', |
||||
chromeLaunchConfig: { |
||||
args: ['--no-sandbox'], |
||||
}, |
||||
// see https://github.com/grafana/grafana/pull/41693#issuecomment-979921463 for context
|
||||
// on why we're ignoring singleValue/react-select-*-placeholder elements
|
||||
hideElements: '#updateVersion, [class*="-singleValue"], [id^="react-select-"][id$="-placeholder"]', |
||||
}, |
||||
|
||||
urls: [ |
||||
{ |
||||
url: '${HOST}/login', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/login', //skip password and login
|
||||
actions: [ |
||||
"wait for element input[name='user'] to be added", |
||||
"set field input[name='user'] to admin", |
||||
"set field input[name='password'] to admin", |
||||
"click element button[data-testid='data-testid Login button']", |
||||
"wait for element button[data-testid='data-testid Skip change password button'] to be visible", |
||||
], |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/?orgId=1', |
||||
wait: 500, |
||||
}, |
||||
{ |
||||
url: '${HOST}/d/O6f11TZWk/panel-tests-bar-gauge', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
...dashboardSettings, |
||||
{ |
||||
url: '${HOST}/?orgId=1&search=open', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/alerting/list', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/datasources', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/org/users', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/org/teams', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/plugins', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/org', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/org/apikeys', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
{ |
||||
url: '${HOST}/dashboards', |
||||
wait: 500, |
||||
rootElement: '.main-view', |
||||
}, |
||||
], |
||||
}; |
||||
|
||||
function myPa11yCiConfiguration(urls, defaults) { |
||||
const HOST_SERVER = process.env.HOST || 'localhost'; |
||||
const PORT_SERVER = process.env.PORT || '3001'; |
||||
for (var idx = 0; idx < urls.length; idx++) { |
||||
urls[idx] = { ...urls[idx], url: urls[idx].url.replace('${HOST}', `${HOST_SERVER}:${PORT_SERVER}`) }; |
||||
} |
||||
return { |
||||
defaults: defaults, |
||||
urls: urls, |
||||
}; |
||||
} |
||||
|
||||
module.exports = myPa11yCiConfiguration(config.urls, config.defaults); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,18 +1,18 @@ |
||||
import { Registry } from '@grafana/data'; |
||||
|
||||
import { fieldNameByRegexMatcherItem } from './FieldNameByRegexMatcherEditor'; |
||||
import { fieldNameMatcherItem } from './FieldNameMatcherEditor'; |
||||
import { fieldNamesMatcherItem } from './FieldNamesMatcherEditor'; |
||||
import { fieldTypeMatcherItem } from './FieldTypeMatcherEditor'; |
||||
import { fieldValueMatcherItem } from './FieldValueMatcher'; |
||||
import { fieldsByFrameRefIdItem } from './FieldsByFrameRefIdMatcher'; |
||||
import { getFieldNameByRegexMatcherItem } from './FieldNameByRegexMatcherEditor'; |
||||
import { getFieldNameMatcherItem } from './FieldNameMatcherEditor'; |
||||
import { getFieldNamesMatcherItem } from './FieldNamesMatcherEditor'; |
||||
import { getFieldTypeMatcherItem } from './FieldTypeMatcherEditor'; |
||||
import { getFieldValueMatcherItem } from './FieldValueMatcher'; |
||||
import { getFieldsByFrameRefIdItem } from './FieldsByFrameRefIdMatcher'; |
||||
import { FieldMatcherUIRegistryItem } from './types'; |
||||
|
||||
export const fieldMatchersUI = new Registry<FieldMatcherUIRegistryItem<any>>(() => [ |
||||
fieldNameMatcherItem, |
||||
fieldNameByRegexMatcherItem, |
||||
fieldTypeMatcherItem, |
||||
fieldsByFrameRefIdItem, |
||||
fieldNamesMatcherItem, |
||||
fieldValueMatcherItem, |
||||
getFieldNameMatcherItem(), |
||||
getFieldNameByRegexMatcherItem(), |
||||
getFieldTypeMatcherItem(), |
||||
getFieldsByFrameRefIdItem(), |
||||
getFieldNamesMatcherItem(), |
||||
getFieldValueMatcherItem(), |
||||
]); |
||||
|
@ -0,0 +1,22 @@ |
||||
# Pa11y accessability tests |
||||
|
||||
We use pa11y to run some automated simple accessability tests. They're ran with dagger to help orchestrate starting server + tests in a reproducable manner. |
||||
|
||||
To run the tests locally: |
||||
|
||||
1. Install dagger locally https://docs.dagger.io/install/ |
||||
2. Grab the grafana.tar.gz artifact by either |
||||
1. Downloading it from the Github Action artifact from your PR |
||||
1. Build it locally with: |
||||
```sh |
||||
dagger run go run ./pkg/build/cmd artifacts -a targz:grafana:linux/amd64 --grafana-dir="$PWD" > dist/files.txt |
||||
cat dist/files.txt # Will output the path to the grafana.tar.gz |
||||
``` |
||||
3. Run the dagger pipeline with: |
||||
```sh |
||||
dagger -v run go run ./pkg/build/a11y --package=(full path to .tar.gz) --results=./pa11y-ci-results.json |
||||
``` |
||||
The JSON results file will be saved to the file from the `--results` arg |
||||
4. If they fail and you want to see the full output |
||||
1. Run the dagger command with `dagger -vE [...]` |
||||
2. At the end, arrow up to the exec pa11y-ci segment and hit Enter |
@ -1,25 +1,48 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"dagger.io/dagger" |
||||
) |
||||
|
||||
func RunTest( |
||||
ctx context.Context, |
||||
d *dagger.Client, |
||||
svc *dagger.Service, |
||||
src *dagger.Directory, cache *dagger.CacheVolume, |
||||
nodeVersion, runnerFlags string) *dagger.Container { |
||||
command := fmt.Sprintf( |
||||
"./e2e-runner a11y --start-grafana=false"+ |
||||
" --grafana-host grafana --grafana-port 3001 %s", runnerFlags) |
||||
|
||||
return GrafanaFrontend(d, cache, nodeVersion, src). |
||||
WithExec([]string{"/bin/sh", "-c", "apt-get update && apt-get install -y git curl"}). |
||||
WithExec([]string{"curl", "-LO", "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"}). |
||||
WithExec([]string{"apt-get", "install", "-y", "./google-chrome-stable_current_amd64.deb"}). |
||||
grafanaService *dagger.Service, |
||||
pa11yConfig *dagger.File, |
||||
noThresholdFail bool, |
||||
pa11yResultsPath string, |
||||
) (*dagger.Container, error) { |
||||
// docker-puppeteer container already has Chrome and Pa11y installed in it
|
||||
pa11yContainer := d.Container().From("grafana/docker-puppeteer:1.1.0"). |
||||
WithWorkdir("/src"). |
||||
WithServiceBinding("grafana", svc). |
||||
WithExec([]string{"/bin/bash", "-c", command}, dagger.ContainerWithExecOpts{Expect: dagger.ReturnTypeAny}) |
||||
WithExec([]string{"mkdir", "-p", "./screenshots"}). // not yet exported
|
||||
WithEnvVariable("HOST", grafanaHost). |
||||
WithEnvVariable("PORT", fmt.Sprint(grafanaPort)) |
||||
|
||||
if noThresholdFail { |
||||
// This logic is non-intuitive - --no-threshold-fail will make pa11y fail (by removing thresholds from the config)
|
||||
// so it can write all violations to the results file. This failure is then ignored by the caller in main.go.
|
||||
// Otherwise, pa11y ignores violations if they're within the thresholds and doesn't include them in the results file
|
||||
pa11yContainer = pa11yContainer. |
||||
WithEnvVariable("NO_THRESHOLDS", "true") |
||||
} |
||||
|
||||
pa11yContainer = pa11yContainer. |
||||
WithServiceBinding(grafanaHost, grafanaService). |
||||
WithMountedFile("pa11yci-config.js", pa11yConfig). |
||||
WithExec([]string{"pa11y-ci", "--config", "pa11yci-config.js"}, dagger.ContainerWithExecOpts{ |
||||
Expect: dagger.ReturnTypeAny, // allow this to fail here so we can handle non-zero exit codes at the caller
|
||||
}) |
||||
|
||||
if pa11yResultsPath != "" { |
||||
_, err := pa11yContainer.File("/src/pa11y-ci-results.json").Export(ctx, pa11yResultsPath) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("failed to get pa11y results: %w", err) |
||||
} |
||||
} |
||||
|
||||
return pa11yContainer, nil |
||||
} |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue