mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
ci(e2e): Run Tests in Pipelines (#3903)
* cy10 changes
* test: setup local e2e env
* test(e2e): migrate e2e setup
* add more config
* make e2e setup work
* align variables
* fix config
* skip mfa
* set user register to false
* read ids from database if not provided
* don't read ids withing env file
* fix escaping in id queries
* fix project root
* export projectRoot path
* export projectRoot
* add e2e-setup.sh
* specify GOOS and GOARCH for dockerfile compatible binary
* add org default redirect uri
* correctly initialize org policy
* await ids
* fix awaiting ids
* fix cypress configuration
* fix some tests
* initial compose setup
* fix working directory
* fix references
* make tests less flaky
* run go tests
* compose works until e2e-setup incl
* pass created e2e sa key
* make cypress run
* derive e2e orgs domain from baseurl
* use host from baseurl for setup ctx
* move defaults.yaml back to cmd pkg
* just create org owner
* Don't render element if no roles are passed
* use map instead of switchMap
* fix e2e tests
* added testdata for e3e
* zipped dump
* removed dumpDir
* cypress workflow with compose
* quote name
* cleanup vars
* eliminate need for e2e setup
* compose has no builds anymore
* use compose run and zitadel nw
* test e2e on pr (#4114)
* test e2e on pr
* install goreleaser
* install npm dev dependencies
* run cypress wf
* dynamic release version
* skip flaky user tests
* skip flaky permissions test
* cache docker layers in pipeline
* Update .github/workflows/cypress.yml
Co-authored-by: Florian Forster <florian@caos.ch>
* align goreleaser version
* get rid of install.sh
* remove cypress-terminal-report
* Revert "remove cypress-terminal-report"
This reverts commit 254b5a1f87
.
* just one npm e2e:build command
* cache npm dependencies
* install node modules using docker
* dedicated e2e context
* fix syntax
* don't copy node modules from goreleaser
* add npm-copy target
* add tsconfig.json
* remove docker caching
* deleted unneeded shellscript
* naming and cleanup
Co-authored-by: Florian Forster <florian@caos.ch>
Co-authored-by: Christian Jakob <christian@caos.ch>
* cleanup
Co-authored-by: Elio Bischof <eliobischof@gmail.com>
Co-authored-by: Christian Jakob <christian@caos.ch>
Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
parent
ce85397050
commit
fc99ec87c5
3
.artifacts/zitadel/.gitignore
vendored
Normal file
3
.artifacts/zitadel/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*
|
||||
!.gitignore
|
||||
!.gitkeep
|
44
.github/workflows/e2e.yml
vendored
Normal file
44
.github/workflows/e2e.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: "ZITADEL e2e Tests"
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
releaseversion:
|
||||
description: 'Release version to test'
|
||||
required: true
|
||||
default: 'latest'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Set TAG env manual trigger
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: echo "RELEASE_VERSION=${{ github.event.inputs.releaseversion }}" >> $GITHUB_ENV
|
||||
- name: Set TAG env on release
|
||||
if: github.event_name == 'release'
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
driver: docker
|
||||
install: true
|
||||
- name: Test
|
||||
run: docker compose run e2e
|
||||
working-directory: e2e
|
||||
env:
|
||||
ZITADEL_IMAGE: ghcr.io/zitadel/zitadel:${RELEASE_VERSION}
|
||||
- name: Archive production tests
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: production-tests
|
||||
path: |
|
||||
e2e/cypress/results
|
||||
e2e/cypress/videos
|
||||
e2e/cypress/screenshots
|
||||
retention-days: 30
|
45
.github/workflows/zitadel-pr.yml
vendored
45
.github/workflows/zitadel-pr.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
Go:
|
||||
Test:
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
@ -18,28 +18,33 @@ jobs:
|
||||
with:
|
||||
driver: docker
|
||||
install: true
|
||||
- name: Test
|
||||
run: docker build -f build/grpc/Dockerfile -t zitadel-base:local . && docker build -f build/zitadel/Dockerfile . -t zitadel-go-test --target go-codecov -o .artifacts/codecov
|
||||
- name: Install GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
install-only: true
|
||||
version: v1.8.3
|
||||
- name: Build and Unit Test
|
||||
run: GOOS="linux" GOARCH="amd64" goreleaser build --snapshot --single-target --rm-dist --output .artifacts/zitadel/zitadel
|
||||
- name: Publish go coverage
|
||||
uses: codecov/codecov-action@v3.1.0
|
||||
with:
|
||||
file: .artifacts/codecov/profile.cov
|
||||
name: go-codecov
|
||||
|
||||
Angular:
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
steps:
|
||||
- name: Source checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
# As goreleaser doesn't build a dockerfile in snapshot mode, we have to build it here
|
||||
- name: Build Docker Image
|
||||
run: docker build -t zitadel:pr --file build/Dockerfile .artifacts/zitadel
|
||||
- name: Run E2E Tests
|
||||
run: docker compose run e2e
|
||||
working-directory: e2e
|
||||
env:
|
||||
ZITADEL_IMAGE: zitadel:pr
|
||||
- name: Archive Test Results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
driver: docker
|
||||
install: true
|
||||
- name: Test
|
||||
run: docker build -f build/grpc/Dockerfile -t zitadel-base:local . && docker build -f build/console/Dockerfile . -t zitadel-npm-base --target angular-build
|
||||
|
||||
name: pull-request-tests
|
||||
path: |
|
||||
e2e/cypress/results
|
||||
e2e/cypress/videos
|
||||
e2e/cypress/screenshots
|
||||
retention-days: 30
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -61,6 +61,7 @@ openapi/**/*.json
|
||||
build/local/*.env
|
||||
migrations/cockroach/migrate_cloud.go
|
||||
.notifications
|
||||
.artifacts
|
||||
/.artifacts/*
|
||||
!/.artifacts/zitadel
|
||||
/zitadel
|
||||
|
||||
|
@ -16,7 +16,7 @@ before:
|
||||
- docker build -f build/zitadel/Dockerfile . -t zitadel-go-base --target go-copy -o .artifacts/grpc/go-client
|
||||
- sh -c "cp -r .artifacts/grpc/go-client/* ."
|
||||
- docker build -f build/console/Dockerfile . -t zitadel-npm-base --target npm-copy -o .artifacts/grpc/js-client
|
||||
- docker build -f build/console/Dockerfile . -t zitadel-npm-base --target angular-export -o .artifacts/console
|
||||
- docker build -f build/console/Dockerfile . -t zitadel-npm-console --target angular-export -o .artifacts/console
|
||||
- sh -c "cp -r .artifacts/console/* internal/api/ui/console/static/"
|
||||
|
||||
builds:
|
||||
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"supportFile": "./cypress/support/index.ts",
|
||||
"reporter": "mochawesome",
|
||||
"reporterOptions": {
|
||||
"reportDir": "cypress/results",
|
||||
"overwrite": false,
|
||||
"html": true,
|
||||
"json": true
|
||||
},
|
||||
"chromeWebSecurity": false,
|
||||
"experimentalSessionSupport": true,
|
||||
"trashAssetsBeforeRuns": false
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ACTION=$1
|
||||
ENVFILE=$2
|
||||
|
||||
shift
|
||||
shift
|
||||
|
||||
projectRoot=".."
|
||||
|
||||
set -a; source $ENVFILE; set +a
|
||||
|
||||
NPX=""
|
||||
if ! command -v cypress &> /dev/null; then
|
||||
NPX="npx"
|
||||
fi
|
||||
|
||||
$NPX cypress $ACTION \
|
||||
--port ${E2E_CYPRESS_PORT} \
|
||||
--env org="${E2E_ORG}",org_owner_password="${E2E_ORG_OWNER_PW}",org_owner_viewer_password="${E2E_ORG_OWNER_VIEWER_PW}",org_project_creator_password="${E2E_ORG_PROJECT_CREATOR_PW}",login_policy_user_password="${E2E_LOGIN_POLICY_USER_PW}",password_complexity_user_password="${E2E_PASSWORD_COMPLEXITY_USER_PW}",consoleUrl=${E2E_CONSOLE_URL},apiUrl="${E2E_API_URL}",accountsUrl="${E2E_ACCOUNTS_URL}",issuerUrl="${E2E_ISSUER_URL}",serviceAccountKey="${E2E_SERVICEACCOUNT_KEY}",serviceAccountKeyPath="${E2E_SERVICEACCOUNT_KEY_PATH}",otherZitadelIdpInstance="${E2E_OTHER_ZITADEL_IDP_INSTANCE}",zitadelProjectResourceId="${E2E_ZITADEL_PROJECT_RESOURCE_ID}" \
|
||||
"$@"
|
@ -1,50 +0,0 @@
|
||||
import { login, User } from "../../support/login/users";
|
||||
import { Apps, ensureProjectExists, ensureProjectResourceDoesntExist } from "../../support/api/projects";
|
||||
import { apiAuth } from "../../support/api/apiauth";
|
||||
|
||||
describe('applications', () => {
|
||||
|
||||
const testProjectName = 'e2eprojectapplication'
|
||||
const testAppName = 'e2eappundertest'
|
||||
|
||||
;[User.OrgOwner].forEach(user => {
|
||||
|
||||
describe(`as user "${user}"`, () => {
|
||||
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
login(user)
|
||||
apiAuth().then(api => {
|
||||
ensureProjectExists(api, testProjectName).then(projectID => {
|
||||
ensureProjectResourceDoesntExist(api, projectID, Apps, testAppName).then(() => {
|
||||
cy.visit(`${Cypress.env('consoleUrl')}/projects/${projectID}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('add app', () => {
|
||||
cy.get('mat-spinner')
|
||||
cy.get('mat-spinner').should('not.exist')
|
||||
cy.get('[data-e2e="app-card-add"]').should('be.visible').click()
|
||||
// select webapp
|
||||
cy.get('[formcontrolname="name"]').type(testAppName)
|
||||
cy.get('[for="WEB"]').click()
|
||||
cy.get('[data-e2e="continue-button-nameandtype"]').click()
|
||||
//select authentication
|
||||
cy.get('[for="PKCE"]').click()
|
||||
cy.get('[data-e2e="continue-button-authmethod"]').click()
|
||||
//enter URL
|
||||
cy.get('cnsl-redirect-uris').eq(0).type("https://testurl.org")
|
||||
cy.get('cnsl-redirect-uris').eq(1).type("https://testlogouturl.org")
|
||||
cy.get('[data-e2e="continue-button-redirecturis"]').click()
|
||||
cy.get('[data-e2e="create-button"]').click().then(() => {
|
||||
cy.get('[id*=overlay]').should('exist')
|
||||
})
|
||||
cy.get('.data-e2e-success')
|
||||
cy.wait(200)
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist')
|
||||
//TODO: check client ID/Secret
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,83 +0,0 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureHumanUserExists, ensureUserDoesntExist } from '../../support/api/users';
|
||||
import { login, User, username } from '../../support/login/users';
|
||||
|
||||
describe('humans', () => {
|
||||
const humansPath = `${Cypress.env('consoleUrl')}/users?type=human`;
|
||||
const testHumanUserNameAdd = 'e2ehumanusernameadd';
|
||||
const testHumanUserNameRemove = 'e2ehumanusernameremove';
|
||||
|
||||
[User.OrgOwner].forEach((user) => {
|
||||
describe(`as user "${user}"`, () => {
|
||||
beforeEach(() => {
|
||||
login(user);
|
||||
cy.visit(humansPath);
|
||||
cy.get('[data-cy=timestamp]');
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
before(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then((apiCallProperties) => {
|
||||
ensureUserDoesntExist(apiCallProperties, testHumanUserNameAdd);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a user', () => {
|
||||
cy.get('a[href="/users/create"]').click();
|
||||
cy.url().should('contain', 'users/create');
|
||||
cy.get('[formcontrolname="email"]').type(username('e2ehuman'));
|
||||
//force needed due to the prefilled username prefix
|
||||
cy.get('[formcontrolname="userName"]').type(testHumanUserNameAdd, { force: true });
|
||||
cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname');
|
||||
cy.get('[formcontrolname="lastName"]').type('e2ehumanlastname');
|
||||
cy.get('[formcontrolname="phone"]').type('+41 123456789');
|
||||
cy.get('[data-e2e="create-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
before('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureHumanUserExists(api, testHumanUserNameRemove);
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a human user', () => {
|
||||
cy.contains('tr', testHumanUserNameRemove, { timeout: 1000 })
|
||||
.find('button')
|
||||
//force due to angular hidden buttons
|
||||
.click({ force: true });
|
||||
cy.get('[e2e-data="confirm-dialog-input"]').type(username(testHumanUserNameRemove, Cypress.env('org')));
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
/*
|
||||
describe("users", ()=> {
|
||||
|
||||
before(()=> {
|
||||
cy.consolelogin(Cypress.env('username'), Cypress.env('password'), Cypress.env('consoleUrl'))
|
||||
})
|
||||
|
||||
it('should show personal information', () => {
|
||||
cy.log(`USER: show personal information`);
|
||||
//click on user information
|
||||
cy.get('a[href*="users/me"').eq(0).click()
|
||||
cy.url().should('contain', '/users/me')
|
||||
})
|
||||
|
||||
it('should show users', () => {
|
||||
cy.visit(Cypress.env('consoleUrl') + '/users/list/humans')
|
||||
cy.url().should('contain', 'users/list/humans')
|
||||
})
|
||||
})
|
||||
|
||||
*/
|
@ -1,60 +0,0 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureMachineUserExists, ensureUserDoesntExist } from '../../support/api/users';
|
||||
import { login, User, username } from '../../support/login/users';
|
||||
|
||||
describe('machines', () => {
|
||||
const machinesPath = `${Cypress.env('consoleUrl')}/users?type=machine`;
|
||||
const testMachineUserNameAdd = 'e2emachineusernameadd';
|
||||
const testMachineUserNameRemove = 'e2emachineusernameremove';
|
||||
|
||||
[User.OrgOwner].forEach((user) => {
|
||||
describe(`as user "${user}"`, () => {
|
||||
beforeEach(() => {
|
||||
login(user);
|
||||
cy.visit(machinesPath);
|
||||
cy.get('[data-cy=timestamp]');
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
before(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then((apiCallProperties) => {
|
||||
ensureUserDoesntExist(apiCallProperties, testMachineUserNameAdd);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a machine', () => {
|
||||
cy.get('a[href="/users/create-machine"]').click();
|
||||
cy.url().should('contain', 'users/create-machine');
|
||||
//force needed due to the prefilled username prefix
|
||||
cy.get('[formcontrolname="userName"]').type(testMachineUserNameAdd, { force: true });
|
||||
cy.get('[formcontrolname="name"]').type('e2emachinename');
|
||||
cy.get('[formcontrolname="description"]').type('e2emachinedescription');
|
||||
cy.get('[data-e2e="create-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
before('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureMachineUserExists(api, testMachineUserNameRemove);
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a machine', () => {
|
||||
cy.contains('tr', testMachineUserNameRemove, { timeout: 1000 })
|
||||
.find('button')
|
||||
//force due to angular hidden buttons
|
||||
.click({ force: true });
|
||||
cy.get('[e2e-data="confirm-dialog-input"]').type(username(testMachineUserNameRemove, Cypress.env('org')));
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,78 +0,0 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
|
||||
import { login, User } from '../../support/login/users';
|
||||
|
||||
describe('projects', () => {
|
||||
const testProjectNameCreate = 'e2eprojectcreate';
|
||||
const testProjectNameDeleteList = 'e2eprojectdeletelist';
|
||||
const testProjectNameDeleteGrid = 'e2eprojectdeletegrid';
|
||||
|
||||
[User.OrgOwner].forEach((user) => {
|
||||
describe(`as user "${user}"`, () => {
|
||||
beforeEach(() => {
|
||||
login(user);
|
||||
});
|
||||
|
||||
describe('add project', () => {
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureProjectDoesntExist(api, testProjectNameCreate);
|
||||
});
|
||||
cy.visit(`${Cypress.env('consoleUrl')}/projects`);
|
||||
});
|
||||
|
||||
it('should add a project', () => {
|
||||
cy.get('.add-project-button').click({ force: true });
|
||||
cy.get('input').type(testProjectNameCreate);
|
||||
cy.get('[data-e2e="continue-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove project', () => {
|
||||
describe('list view', () => {
|
||||
beforeEach('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureProjectExists(api, testProjectNameDeleteList);
|
||||
});
|
||||
cy.visit(`${Cypress.env('consoleUrl')}/projects`);
|
||||
});
|
||||
|
||||
it('removes the project', () => {
|
||||
cy.get('[data-e2e=toggle-grid]').click();
|
||||
cy.get('[data-cy=timestamp]');
|
||||
cy.contains('tr', testProjectNameDeleteList, { timeout: 1000 })
|
||||
.find('[data-e2e=delete-project-button]')
|
||||
.click({ force: true });
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('grid view', () => {
|
||||
beforeEach('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureProjectExists(api, testProjectNameDeleteGrid);
|
||||
});
|
||||
cy.visit(`${Cypress.env('consoleUrl')}/projects`);
|
||||
});
|
||||
|
||||
it('removes the project', () => {
|
||||
cy.contains('[data-e2e=grid-card]', testProjectNameDeleteGrid)
|
||||
.find('[data-e2e=delete-project-button]')
|
||||
.trigger('mouseover')
|
||||
.click();
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,85 +0,0 @@
|
||||
import { apiAuth, apiCallProperties } from "../../support/api/apiauth";
|
||||
import { Policy, resetPolicy } from "../../support/api/policies";
|
||||
import { login, User } from "../../support/login/users";
|
||||
|
||||
describe("private labeling", ()=> {
|
||||
|
||||
const orgPath = `${Cypress.env('consoleUrl')}/org`
|
||||
|
||||
;[User.OrgOwner].forEach(user => {
|
||||
|
||||
describe(`as user "${user}"`, () => {
|
||||
|
||||
let api: apiCallProperties
|
||||
|
||||
|
||||
beforeEach(()=> {
|
||||
login(user)
|
||||
cy.visit(orgPath)
|
||||
// TODO: Why force?
|
||||
cy.contains('[data-e2e=policy-card]', 'Private Labeling').contains('button', 'Modify').click({force: true}) // TODO: select data-e2e
|
||||
})
|
||||
|
||||
customize('white', user)
|
||||
customize('dark', user)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function customize(theme: string, user: User) {
|
||||
|
||||
describe(`${theme} theme`, () => {
|
||||
|
||||
beforeEach(() => {
|
||||
apiAuth().then(api => {
|
||||
resetPolicy(api, Policy.Label)
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip('logo', () => {
|
||||
|
||||
beforeEach('expand logo category', () => {
|
||||
cy.contains('[data-e2e=policy-category]', 'Logo').click() // TODO: select data-e2e
|
||||
cy.fixture('logo.png').as('logo')
|
||||
})
|
||||
|
||||
it('should update a logo', () => {
|
||||
cy.get('[data-e2e=image-part-logo]').find('input').then(function (el) {
|
||||
const blob = Cypress.Blob.base64StringToBlob(this.logo, 'image/png')
|
||||
const file = new File([blob], 'images/logo.png', { type: 'image/png' })
|
||||
const list = new DataTransfer()
|
||||
|
||||
list.items.add(file)
|
||||
const myFileList = list.files
|
||||
|
||||
el[0].files = myFileList
|
||||
el[0].dispatchEvent(new Event('change', { bubbles: true }))
|
||||
})
|
||||
})
|
||||
it('should delete a logo')
|
||||
})
|
||||
it('should update an icon')
|
||||
it('should delete an icon')
|
||||
it.skip('should update the background color', () => {
|
||||
cy.contains('[data-e2e=color]', 'Background Color').find('button').click() // TODO: select data-e2e
|
||||
cy.get('color-editable-input').find('input').clear().type('#ae44dc')
|
||||
cy.get('[data-e2e=save-colors-button]').click()
|
||||
cy.get('[data-e2e=header-user-avatar]').click()
|
||||
cy.contains('Logout All Users').click() // TODO: select data-e2e
|
||||
login(User.LoginPolicyUser, true, null, () => {
|
||||
cy.pause()
|
||||
})
|
||||
})
|
||||
it('should update the primary color')
|
||||
it('should update the warning color')
|
||||
it('should update the font color')
|
||||
it('should update the font style')
|
||||
it('should hide the loginname suffix')
|
||||
it('should show the loginname suffix')
|
||||
it('should hide the watermark')
|
||||
it('should show the watermark')
|
||||
it('should show the current configuration')
|
||||
it('should reset the policy')
|
||||
})
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
module.exports = (on, config) => {
|
||||
|
||||
require('cypress-terminal-report/src/installLogsPrinter')(on);
|
||||
|
||||
config.defaultCommandTimeout = 10_000
|
||||
|
||||
config.env.parsedServiceAccountKey = config.env.serviceAccountKey
|
||||
if (config.env.serviceAccountKeyPath) {
|
||||
config.env.parsedServiceAccountKey = JSON.parse(readFileSync(config.env.serviceAccountKeyPath, 'utf-8'))
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import { sign } from 'jsonwebtoken'
|
||||
|
||||
export interface apiCallProperties {
|
||||
authHeader: string
|
||||
mgntBaseURL: string
|
||||
}
|
||||
|
||||
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
||||
const apiUrl = Cypress.env('apiUrl')
|
||||
const issuerUrl = Cypress.env('issuerUrl')
|
||||
const zitadelProjectResourceID = (<string>Cypress.env('zitadelProjectResourceId')).replace('bignumber-', '')
|
||||
|
||||
const key = Cypress.env("parsedServiceAccountKey")
|
||||
|
||||
const now = new Date().getTime()
|
||||
const iat = Math.floor(now / 1000)
|
||||
const exp = Math.floor(new Date(now + 1000 * 60 * 55).getTime() / 1000) // 55 minutes
|
||||
const bearerToken = sign({
|
||||
iss: key.userId,
|
||||
sub: key.userId,
|
||||
aud: `${issuerUrl}`,
|
||||
iat: iat,
|
||||
exp: exp
|
||||
}, key.key, {
|
||||
header: {
|
||||
alg: "RS256",
|
||||
kid: key.keyId
|
||||
}
|
||||
})
|
||||
|
||||
return cy.request({
|
||||
method: 'POST',
|
||||
url: `${apiUrl}/oauth/v2/token`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: {
|
||||
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
scope: `openid urn:zitadel:iam:org:project:id:${zitadelProjectResourceID}:aud`,
|
||||
assertion: bearerToken,
|
||||
}
|
||||
}).its('body.access_token').then(token => {
|
||||
|
||||
return <apiCallProperties>{
|
||||
authHeader: `Bearer ${token}`,
|
||||
mgntBaseURL: `${apiUrl}/management/v1/`,
|
||||
}
|
||||
})
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
export enum User {
|
||||
OrgOwner = 'org_owner',
|
||||
OrgOwnerViewer = 'org_owner_viewer',
|
||||
OrgProjectCreator = 'org_project_creator',
|
||||
LoginPolicyUser = 'login_policy_user',
|
||||
PasswordComplexityUser = 'password_complexity_user',
|
||||
IAMAdminUser = "zitadel-admin"
|
||||
}
|
||||
|
||||
export function login(user:User, force?: boolean, pw?: string, onUsernameScreen?: () => void, onPasswordScreen?: () => void, onAuthenticated?: () => void): void {
|
||||
let creds = credentials(user, pw)
|
||||
|
||||
const accountsUrl: string = Cypress.env('accountsUrl')
|
||||
const consoleUrl: string = Cypress.env('consoleUrl')
|
||||
const otherZitadelIdpInstance: boolean = Cypress.env('otherZitadelIdpInstance')
|
||||
|
||||
cy.session(creds.username, () => {
|
||||
|
||||
const cookies = new Map<string, string>()
|
||||
|
||||
if (otherZitadelIdpInstance) {
|
||||
cy.intercept({
|
||||
method: 'GET',
|
||||
url: `${accountsUrl}/login*`,
|
||||
times: 1
|
||||
}, (req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies)
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies)
|
||||
})
|
||||
}).as('login')
|
||||
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: `${accountsUrl}/loginname*`,
|
||||
times: 1
|
||||
}, (req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies)
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies)
|
||||
})
|
||||
}).as('loginName')
|
||||
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: `${accountsUrl}/password*`,
|
||||
times: 1
|
||||
}, (req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies)
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies)
|
||||
})
|
||||
}).as('password')
|
||||
|
||||
cy.intercept({
|
||||
method: 'GET',
|
||||
url: `${accountsUrl}/success*`,
|
||||
times: 1
|
||||
}, (req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies)
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies)
|
||||
})
|
||||
}).as('success')
|
||||
|
||||
cy.intercept({
|
||||
method: 'GET',
|
||||
url: `${accountsUrl}/oauth/v2/authorize/callback*`,
|
||||
times: 1
|
||||
}, (req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies)
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies)
|
||||
})
|
||||
}).as('callback')
|
||||
|
||||
cy.intercept({
|
||||
method: 'GET',
|
||||
url: `${accountsUrl}/oauth/v2/authorize*`,
|
||||
times: 1,
|
||||
}, (req) => {
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
cy.visit(`${consoleUrl}/loginname`, { retryOnNetworkFailure: true });
|
||||
|
||||
otherZitadelIdpInstance && cy.wait('@login')
|
||||
onUsernameScreen ? onUsernameScreen() : null
|
||||
cy.get('#loginName').type(creds.username)
|
||||
cy.get('#submit-button').click()
|
||||
|
||||
otherZitadelIdpInstance && cy.wait('@loginName')
|
||||
onPasswordScreen ? onPasswordScreen() : null
|
||||
cy.get('#password').type(creds.password)
|
||||
cy.get('#submit-button').click()
|
||||
|
||||
onAuthenticated ? onAuthenticated() : null
|
||||
|
||||
otherZitadelIdpInstance && cy.wait('@callback')
|
||||
|
||||
cy.location('pathname', {timeout: 5 * 1000}).should('eq', '/');
|
||||
|
||||
}, {
|
||||
validate: () => {
|
||||
|
||||
if (force) {
|
||||
throw new Error("clear session");
|
||||
}
|
||||
|
||||
cy.visit(`${consoleUrl}/users/me`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function username(withoutDomain: string, project?: string): string {
|
||||
return `${withoutDomain}@${project ? `${project}.` : ''}${host(Cypress.env('apiUrl')).replace('api.', '')}`
|
||||
}
|
||||
|
||||
function credentials(user: User, pw?: string) {
|
||||
const isAdmin = user == User.IAMAdminUser
|
||||
return {
|
||||
username: username(isAdmin ? user : `${user}_user_name`, isAdmin ? 'caos-ag' : Cypress.env('org')),
|
||||
password: pw ? pw : Cypress.env(`${user}_password`)
|
||||
}
|
||||
}
|
||||
|
||||
function updateCookies(newCookies: string[] | undefined, currentCookies: Map<string, string>) {
|
||||
if (newCookies === undefined) {
|
||||
return
|
||||
}
|
||||
newCookies.forEach(cs => {
|
||||
cs.split('; ').forEach(cookie => {
|
||||
const idx = cookie.indexOf('=')
|
||||
currentCookies.set(cookie.substring(0,idx), cookie.substring(idx+1))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function requestCookies(currentCookies: Map<string, string>): string[] {
|
||||
let list: Array<string> = []
|
||||
currentCookies.forEach((val, key) => {
|
||||
list.push(key+"="+val)
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
export function host(url: string): string {
|
||||
return stripPort(stripProtocol(url))
|
||||
}
|
||||
|
||||
function stripPort(s: string): string {
|
||||
const idx = s.indexOf(":")
|
||||
return idx === -1 ? s : s.substring(0,idx)
|
||||
}
|
||||
|
||||
function stripProtocol(url: string): string {
|
||||
return url.replace('http://', '').replace('https://', '')
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
E2E_CYPRESS_PORT=5003
|
||||
E2E_ORG=e2e-tests
|
||||
E2E_ORG_OWNER_PW=Password1!
|
||||
E2E_ORG_OWNER_VIEWER_PW=Password1!
|
||||
E2E_ORG_PROJECT_CREATOR_PW=Password1!
|
||||
E2E_PASSWORD_COMPLEXITY_USER_PW=Password1!
|
||||
E2E_LOGIN_POLICY_USER_PW=Password1!
|
||||
E2E_SERVICEACCOUNT_KEY_PATH="${projectRoot}/.keys/e2e.json"
|
||||
E2E_CONSOLE_URL="http://localhost:4200"
|
||||
E2E_API_URL="http://localhost:50002"
|
||||
E2E_ACCOUNTS_URL="http://localhost:50003"
|
||||
E2E_ISSUER_URL="http://localhost:50002/oauth/v2"
|
||||
E2E_OTHER_ZITADEL_IDP_INSTANCE=false
|
||||
E2E_ZITADEL_PROJECT_RESOURCE_ID="bignumber-$(echo -n $(docker compose -f ${projectRoot}/build/local/docker-compose-local.yml exec --no-TTY db cockroach sql --insecure --execute "select aggregate_id from eventstore.events where event_type = 'project.added' and event_data = '{\"name\": \"Zitadel\"}';" --format tsv) | cut -d " " -f 2)"
|
2897
console/package-lock.json
generated
2897
console/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,9 +6,7 @@
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"prodbuild": "ng build --configuration production --base-href=/ui/console/",
|
||||
"lint": "ng lint && stylelint './src/**/*.scss' --syntax scss",
|
||||
"e2e": "./cypress.sh run e2e.env",
|
||||
"e2e:open": "./cypress.sh open e2e.env"
|
||||
"lint": "ng lint && stylelint './src/**/*.scss' --syntax scss"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@ -68,18 +66,14 @@
|
||||
"@typescript-eslint/eslint-plugin": "5.30.4",
|
||||
"@typescript-eslint/parser": "5.30.4",
|
||||
"codelyzer": "^6.0.0",
|
||||
"cypress": "^10.1.0",
|
||||
"cypress-terminal-report": "^4.0.1",
|
||||
"eslint": "^8.18.0",
|
||||
"jasmine-core": "~4.2.0",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.0.0",
|
||||
"mochawesome": "^7.1.2",
|
||||
"prettier": "^2.4.1",
|
||||
"protractor": "~7.0.0",
|
||||
"stylelint": "^13.10.0",
|
||||
|
@ -6,7 +6,7 @@
|
||||
<span>ESC</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="ActionKeysType.ADD" class="action-keys-row">
|
||||
<div *ngSwitchCase="ActionKeysType.ADD" class="action-keys-row" [attr.data-e2e]="'action-key-add'">
|
||||
<div class="action-key">
|
||||
<div class="key-overlay"></div>
|
||||
<span>N</span>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<p class="length">
|
||||
<span>{{ length }} </span>{{ 'PAGINATOR.COUNT' | translate }}
|
||||
</p>
|
||||
<p class="ts cnsl-secondary-text" *ngIf="timestamp">
|
||||
<p class="ts cnsl-secondary-text" *ngIf="timestamp" [attr.data-e2e]="'timestamp'">
|
||||
{{ timestamp | timestampToDate | localizedDate: 'EEEE dd. MMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
<ng-template cnslHasRole [hasRole]="['project.role.write:' + projectId, 'project.role.write']" actions>
|
||||
<a *ngIf="actionsVisible" [disabled]="disabled" [routerLink]="[ '/projects', projectId, 'roles', 'create']"
|
||||
color="primary" class="cnsl-action-button" mat-raised-button [attr.data-e2e]="'add-new-role'">
|
||||
<mat-icon class="icon">add</mat-icon>
|
||||
color="primary" class="cnsl-action-button" mat-raised-button>
|
||||
<mat-icon [attr.data-e2e]="'add-new-role'" class="icon">add</mat-icon>
|
||||
<span>{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
<cnsl-action-keys (actionTriggered)="gotoRouterLink([ '/projects', projectId, 'roles', 'create'])">
|
||||
</cnsl-action-keys>
|
||||
|
@ -3,7 +3,8 @@
|
||||
<ng-content select="[hoverActions]"></ng-content>
|
||||
<ng-content select="[actions]"></ng-content>
|
||||
<button (click)="$event.stopPropagation()" *ngIf="hasActions" class="table-actions-trigger" mat-icon-button
|
||||
[matMenuTriggerFor]="actions">
|
||||
[matMenuTriggerFor]="actions"
|
||||
[attr.data-e2e]="'table-actions-button'">
|
||||
<mat-icon>more_horiz</mat-icon>
|
||||
</button>
|
||||
|
||||
|
@ -84,7 +84,8 @@
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<cnsl-table-actions>
|
||||
<button actions *ngIf="project.id !== zitadelProjectId" color="warn" mat-icon-button
|
||||
matTooltip="{{'ACTIONS.DELETE' | translate}}" (click)="deleteProject(project.id, project.name)">
|
||||
matTooltip="{{'ACTIONS.DELETE' | translate}}" (click)="deleteProject(project.id, project.name)"
|
||||
[attr.data-e2e]="'delete-project-button'">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</cnsl-table-actions>
|
||||
|
@ -13,11 +13,13 @@
|
||||
label="{{ 'USER.TABLE.TYPES.HUMAN' | translate }}"
|
||||
(clicked)="setType(Type.TYPE_HUMAN)"
|
||||
[active]="type === Type.TYPE_HUMAN"
|
||||
[attr.data-e2e]="'list-humans'"
|
||||
></cnsl-nav-toggle>
|
||||
<cnsl-nav-toggle
|
||||
label="{{ 'USER.TABLE.TYPES.MACHINE' | translate }}"
|
||||
(clicked)="setType(Type.TYPE_MACHINE)"
|
||||
[active]="type === Type.TYPE_MACHINE"
|
||||
[attr.data-e2e]="'list-machines'"
|
||||
></cnsl-nav-toggle>
|
||||
</div>
|
||||
|
||||
@ -56,6 +58,7 @@
|
||||
mat-raised-button
|
||||
[disabled]="!canWrite"
|
||||
class="cnsl-action-button"
|
||||
[attr.data-e2e]="'create-user-button'"
|
||||
>
|
||||
<mat-icon class="icon">add</mat-icon>
|
||||
<span>{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
@ -194,6 +197,7 @@
|
||||
color="warn"
|
||||
(click)="deleteUser(user)"
|
||||
[disabled]="!canWrite || !canDelete"
|
||||
[attr.e2e-data]="!canWrite || !canDelete ? 'disabled-delete-button' : 'enabled-delete-button'"
|
||||
mat-icon-button
|
||||
>
|
||||
<i class="las la-trash"></i>
|
||||
|
1
e2e/.gitignore
vendored
Normal file
1
e2e/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
12
e2e/README.md
Normal file
12
e2e/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Run e2e Tests
|
||||
|
||||
```bash
|
||||
docker compose run e2e
|
||||
```
|
||||
|
||||
# Cleanup e2e Tests
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
41
e2e/cypress.config.ts
Normal file
41
e2e/cypress.config.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
let tokensCache = new Map<string,string>()
|
||||
|
||||
export default defineConfig({
|
||||
reporter: 'mochawesome',
|
||||
|
||||
reporterOptions: {
|
||||
reportDir: 'cypress/results',
|
||||
overwrite: false,
|
||||
html: true,
|
||||
json: true,
|
||||
},
|
||||
|
||||
chromeWebSecurity: false,
|
||||
trashAssetsBeforeRuns: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
|
||||
env: {
|
||||
ORGANIZATION: process.env.CYPRESS_ORGANIZATION || 'zitadel'
|
||||
},
|
||||
|
||||
e2e: {
|
||||
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:8080',
|
||||
experimentalSessionAndOrigin: true,
|
||||
setupNodeEvents(on, config) {
|
||||
|
||||
on('task', {
|
||||
safetoken({key, token}) {
|
||||
tokensCache.set(key,token);
|
||||
return null
|
||||
}
|
||||
})
|
||||
on('task', {
|
||||
loadtoken({key}): string | null {
|
||||
return tokensCache.get(key) || null;
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
});
|
42
e2e/cypress/e2e/applications/applications.cy.ts
Normal file
42
e2e/cypress/e2e/applications/applications.cy.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { Apps, ensureProjectExists, ensureProjectResourceDoesntExist } from "../../support/api/projects";
|
||||
import { apiAuth } from "../../support/api/apiauth";
|
||||
|
||||
describe('applications', () => {
|
||||
|
||||
const testProjectName = 'e2eprojectapplication'
|
||||
const testAppName = 'e2eappundertest'
|
||||
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then(api => {
|
||||
ensureProjectExists(api, testProjectName).then(projectID => {
|
||||
ensureProjectResourceDoesntExist(api, projectID, Apps, testAppName).then(() => {
|
||||
cy.visit(`/ui/console/projects/${projectID}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('add app', () => {
|
||||
cy.get('mat-spinner')
|
||||
cy.get('mat-spinner').should('not.exist')
|
||||
cy.get('[data-e2e="app-card-add"]').should('be.visible').click()
|
||||
// select webapp
|
||||
cy.get('[formcontrolname="name"]').type(testAppName)
|
||||
cy.get('[for="WEB"]').click()
|
||||
cy.get('[data-e2e="continue-button-nameandtype"]').click()
|
||||
//select authentication
|
||||
cy.get('[for="PKCE"]').click()
|
||||
cy.get('[data-e2e="continue-button-authmethod"]').click()
|
||||
//enter URL
|
||||
cy.get('cnsl-redirect-uris').eq(0).type("https://testurl.org")
|
||||
cy.get('cnsl-redirect-uris').eq(1).type("https://testlogouturl.org")
|
||||
cy.get('[data-e2e="continue-button-redirecturis"]').click()
|
||||
cy.get('[data-e2e="create-button"]').click().then(() => {
|
||||
cy.get('[id*=overlay]').should('exist')
|
||||
})
|
||||
cy.get('.data-e2e-success')
|
||||
cy.wait(200)
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist')
|
||||
//TODO: check client ID/Secret
|
||||
})
|
||||
})
|
57
e2e/cypress/e2e/humans/humans.cy.ts
Normal file
57
e2e/cypress/e2e/humans/humans.cy.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureHumanUserExists, ensureUserDoesntExist } from '../../support/api/users';
|
||||
import { loginname } from '../../support/login/users';
|
||||
|
||||
describe.skip('humans', () => {
|
||||
const humansPath = `/ui/console/users?type=human`;
|
||||
const testHumanUserNameAdd = 'e2ehumanusernameadd';
|
||||
const testHumanUserNameRemove = 'e2ehumanusernameremove';
|
||||
|
||||
describe('add', () => {
|
||||
before(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then((apiCallProperties) => {
|
||||
ensureUserDoesntExist(apiCallProperties, testHumanUserNameAdd).then(()=>{
|
||||
cy.visit(humansPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a user', () => {
|
||||
cy.get('[data-e2e="action-key-add"]').parents('[data-e2e="create-user-button"]').click();
|
||||
cy.url().should('contain', 'users/create');
|
||||
cy.get('[formcontrolname="email"]').type(loginname('e2ehuman'));
|
||||
//force needed due to the prefilled username prefix
|
||||
cy.get('[formcontrolname="userName"]').type(testHumanUserNameAdd, { force: true });
|
||||
cy.get('[formcontrolname="firstName"]').type('e2ehumanfirstname');
|
||||
cy.get('[formcontrolname="lastName"]').type('e2ehumanlastname');
|
||||
cy.get('[formcontrolname="phone"]').type('+41 123456789');
|
||||
cy.get('[data-e2e="create-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
before('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureHumanUserExists(api, testHumanUserNameRemove).then(()=>{
|
||||
cy.visit(humansPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a human user', () => {
|
||||
cy.contains('tr', testHumanUserNameRemove)
|
||||
// doesn't work, need to force click.
|
||||
// .trigger('mouseover')
|
||||
.find('[e2e-data="enabled-delete-button"]')
|
||||
.click({force: true});
|
||||
cy.get('[e2e-data="confirm-dialog-input"]').click().type(loginname(testHumanUserNameRemove, Cypress.env('org')));
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
55
e2e/cypress/e2e/machines/machines.cy.ts
Normal file
55
e2e/cypress/e2e/machines/machines.cy.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureMachineUserExists, ensureUserDoesntExist } from '../../support/api/users';
|
||||
import { loginname } from '../../support/login/users';
|
||||
|
||||
describe.skip('machines', () => {
|
||||
const machinesPath = `/ui/console/users?type=machine`;
|
||||
const testMachineUserNameAdd = 'e2emachineusernameadd';
|
||||
const testMachineUserNameRemove = 'e2emachineusernameremove';
|
||||
|
||||
describe('add', () => {
|
||||
before(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then((apiCallProperties) => {
|
||||
ensureUserDoesntExist(apiCallProperties, testMachineUserNameAdd).then(()=>{
|
||||
cy.visit(machinesPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a machine', () => {
|
||||
cy.get('[data-e2e="action-key-add"]').parents('[data-e2e="create-user-button"]').click();
|
||||
cy.url().should('contain', 'users/create-machine');
|
||||
//force needed due to the prefilled username prefix
|
||||
cy.get('[formcontrolname="userName"]').type(testMachineUserNameAdd, { force: true });
|
||||
cy.get('[formcontrolname="name"]').type('e2emachinename');
|
||||
cy.get('[formcontrolname="description"]').type('e2emachinedescription');
|
||||
cy.get('[data-e2e="create-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
before('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureMachineUserExists(api, testMachineUserNameRemove).then(()=>{
|
||||
cy.visit(machinesPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a machine', () => {
|
||||
cy.contains('tr', testMachineUserNameRemove, { timeout: 1000 })
|
||||
// doesn't work, need to force click.
|
||||
// .trigger('mouseover')
|
||||
.find('[e2e-data="enabled-delete-button"]')
|
||||
.click({force: true});
|
||||
cy.get('[e2e-data="confirm-dialog-input"]').click().type(loginname(testMachineUserNameRemove, Cypress.env('org')));
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
@ -1,8 +1,7 @@
|
||||
import { apiAuth, apiCallProperties } from "../../support/api/apiauth";
|
||||
import { apiAuth } from "../../support/api/apiauth";
|
||||
import { ensureProjectExists, ensureProjectResourceDoesntExist, Roles } from "../../support/api/projects";
|
||||
import { login, User } from "../../support/login/users";
|
||||
|
||||
describe('permissions', () => {
|
||||
describe.skip('permissions', () => {
|
||||
|
||||
const testProjectName = 'e2eprojectpermission'
|
||||
const testAppName = 'e2eapppermission'
|
||||
@ -11,42 +10,34 @@ describe('permissions', () => {
|
||||
const testRoleGroup = 'e2eroleundertestgroup'
|
||||
const testGrantName = 'e2egrantundertest'
|
||||
|
||||
;[User.OrgOwner].forEach(user => {
|
||||
var projectId: number
|
||||
|
||||
describe(`as user "${user}"`, () => {
|
||||
|
||||
var api: apiCallProperties
|
||||
var projectId: number
|
||||
|
||||
beforeEach(() => {
|
||||
login(user)
|
||||
apiAuth().then(apiCalls => {
|
||||
api = apiCalls
|
||||
ensureProjectExists(apiCalls, testProjectName).then(projId => {
|
||||
projectId = projId
|
||||
cy.visit(`${Cypress.env('consoleUrl')}/projects/${projId}`)
|
||||
})
|
||||
})
|
||||
beforeEach(() => {
|
||||
apiAuth().then(apiCalls => {
|
||||
ensureProjectExists(apiCalls, testProjectName).then(projId => {
|
||||
projectId = projId
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
describe('add role', () => {
|
||||
beforeEach(()=> {
|
||||
ensureProjectResourceDoesntExist(api, projectId, Roles, testRoleName)
|
||||
})
|
||||
|
||||
it('should add a role', () => {
|
||||
cy.get('[data-e2e="add-new-role"]').click()
|
||||
cy.get('[formcontrolname="key"]').type(testRoleName)
|
||||
cy.get('[formcontrolname="displayName"]').type(testRoleDisplay)
|
||||
cy.get('[formcontrolname="group"]').type(testRoleGroup)
|
||||
cy.get('[data-e2e="save-button"]').click()
|
||||
cy.get('.data-e2e-success')
|
||||
cy.wait(200)
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist')
|
||||
})
|
||||
describe('add role', () => {
|
||||
beforeEach(()=> {
|
||||
apiAuth().then((api)=> {
|
||||
ensureProjectResourceDoesntExist(api, projectId, Roles, testRoleName)
|
||||
cy.visit(`/ui/console/projects/${projectId}?id=roles`)
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a role', () => {
|
||||
cy.get('[data-e2e="add-new-role"]').click()
|
||||
cy.get('[formcontrolname="key"]').type(testRoleName)
|
||||
cy.get('[formcontrolname="displayName"]').type(testRoleDisplay)
|
||||
cy.get('[formcontrolname="group"]').type(testRoleGroup)
|
||||
cy.get('[data-e2e="save-button"]').click()
|
||||
cy.get('.data-e2e-success')
|
||||
cy.wait(200)
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
/*
|
||||
@ -54,19 +45,19 @@ describe('permissions', () => {
|
||||
describe('permissions', () => {
|
||||
|
||||
before(()=> {
|
||||
// cy.consolelogin(Cypress.env('username'), Cypress.env('password'), Cypress.env('consoleUrl'))
|
||||
// cy.consolelogin(Cypress.env('username'), Cypress.env('password'), Cypress.config('baseUrl')/ui/console)
|
||||
})
|
||||
|
||||
it('should show projects ', () => {
|
||||
cy.visit(Cypress.env('consoleUrl') + '/projects')
|
||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/projects')
|
||||
cy.url().should('contain', '/projects')
|
||||
})
|
||||
|
||||
it('should add a role', () => {
|
||||
cy.visit(Cypress.env('consoleUrl') + '/org').then(() => {
|
||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/org').then(() => {
|
||||
cy.url().should('contain', '/org');
|
||||
})
|
||||
cy.visit(Cypress.env('consoleUrl') + '/projects').then(() => {
|
||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/projects').then(() => {
|
||||
cy.url().should('contain', '/projects');
|
||||
cy.get('.card').should('contain.text', "newProjectToTest")
|
||||
})
|
||||
@ -77,8 +68,8 @@ describe('permissions', () => {
|
||||
cy.log(url.split('/')[4])
|
||||
projectID = url.split('/')[4]
|
||||
});
|
||||
|
||||
cy.then(() => cy.visit(Cypress.env('consoleUrl') + '/projects/' + projectID +'/roles/create'))
|
||||
|
||||
cy.then(() => cy.visit(Cypress.config('baseUrl')/ui/console + '/projects/' + projectID +'/roles/create'))
|
||||
cy.get('[formcontrolname^=key]').type("newdemorole")
|
||||
cy.get('[formcontrolname^=displayName]').type("newdemodisplayname")
|
||||
cy.get('[formcontrolname^=group]').type("newdemogroupname")
|
||||
@ -88,10 +79,10 @@ describe('permissions', () => {
|
||||
})
|
||||
|
||||
it('should add a grant', () => {
|
||||
cy.visit(Cypress.env('consoleUrl') + '/org').then(() => {
|
||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/org').then(() => {
|
||||
cy.url().should('contain', '/org');
|
||||
})
|
||||
cy.visit(Cypress.env('consoleUrl') + '/projects').then(() => {
|
||||
cy.visit(Cypress.config('baseUrl')/ui/console + '/projects').then(() => {
|
||||
cy.url().should('contain', '/projects');
|
||||
cy.get('.card').should('contain.text', "newProjectToTest")
|
||||
})
|
||||
@ -102,8 +93,8 @@ describe('permissions', () => {
|
||||
cy.log(url.split('/')[4])
|
||||
projectID = url.split('/')[4]
|
||||
});
|
||||
|
||||
cy.then(() => cy.visit(Cypress.env('consoleUrl') + '/grant-create/project/' + projectID ))
|
||||
|
||||
cy.then(() => cy.visit(Cypress.config('baseUrl')/ui/console + '/grant-create/project/' + projectID ))
|
||||
cy.get('input').type("demo")
|
||||
cy.get('[role^=listbox]').filter(`:contains("${Cypress.env("fullUserName")}")`).should('be.visible').click()
|
||||
cy.wait(5000)
|
71
e2e/cypress/e2e/projects/projects.cy.ts
Normal file
71
e2e/cypress/e2e/projects/projects.cy.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
|
||||
|
||||
describe('projects', () => {
|
||||
const testProjectNameCreate = 'e2eprojectcreate';
|
||||
const testProjectNameDeleteList = 'e2eprojectdeletelist';
|
||||
const testProjectNameDeleteGrid = 'e2eprojectdeletegrid';
|
||||
|
||||
describe('add project', () => {
|
||||
beforeEach(`ensure it doesn't exist already`, () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureProjectDoesntExist(api, testProjectNameCreate);
|
||||
});
|
||||
cy.visit(`/ui/console/projects`);
|
||||
});
|
||||
|
||||
it('should add a project', () => {
|
||||
cy.get('.add-project-button').click({ force: true });
|
||||
cy.get('input').type(testProjectNameCreate);
|
||||
cy.get('[data-e2e="continue-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove project', () => {
|
||||
describe('list view', () => {
|
||||
beforeEach('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureProjectExists(api, testProjectNameDeleteList);
|
||||
});
|
||||
cy.visit(`/ui/console/projects`);
|
||||
});
|
||||
|
||||
it('removes the project', () => {
|
||||
cy.get('[data-e2e=toggle-grid]').click();
|
||||
cy.get('[data-e2e=timestamp]');
|
||||
cy.contains('tr', testProjectNameDeleteList, { timeout: 1000 })
|
||||
.find('[data-e2e=delete-project-button]')
|
||||
.click({force: true});
|
||||
cy.get('[e2e-data="confirm-dialog-input"]').type(testProjectNameDeleteList);
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('grid view', () => {
|
||||
beforeEach('ensure it exists', () => {
|
||||
apiAuth().then((api) => {
|
||||
ensureProjectExists(api, testProjectNameDeleteGrid);
|
||||
});
|
||||
cy.visit(`/ui/console/projects`);
|
||||
});
|
||||
|
||||
it('removes the project', () => {
|
||||
cy.contains('[data-e2e=grid-card]', testProjectNameDeleteGrid)
|
||||
.find('[data-e2e=delete-project-button]')
|
||||
.trigger('mouseover')
|
||||
.click();
|
||||
cy.get('[e2e-data="confirm-dialog-input"]').type(testProjectNameDeleteGrid);
|
||||
cy.get('[e2e-data="confirm-dialog-button"]').click();
|
||||
cy.get('.data-e2e-success');
|
||||
cy.wait(200);
|
||||
cy.get('.data-e2e-failure', { timeout: 0 }).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
0
e2e/cypress/e2e/register/register.cy.ts
Normal file
0
e2e/cypress/e2e/register/register.cy.ts
Normal file
@ -4,7 +4,7 @@ import { login, User } from "../../support/login/users";
|
||||
|
||||
describe("login policy", ()=> {
|
||||
|
||||
const orgPath = `${Cypress.env('consoleUrl')}/org`
|
||||
const orgPath = `/ui/console/org`
|
||||
|
||||
;[User.OrgOwner].forEach(user => {
|
||||
|
@ -2,7 +2,7 @@ import { login, User } from "../../support/login/users";
|
||||
|
||||
describe("password complexity", ()=> {
|
||||
|
||||
const orgPath = `${Cypress.env('consoleUrl')}/org`
|
||||
const orgPath = `/ui/console/org`
|
||||
const testProjectName = 'e2eproject'
|
||||
|
||||
;[User.OrgOwner].forEach(user => {
|
79
e2e/cypress/e2e/settings/private-labeling.cy.ts
Normal file
79
e2e/cypress/e2e/settings/private-labeling.cy.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { apiAuth, apiCallProperties } from '../../support/api/apiauth';
|
||||
import { Policy, resetPolicy } from '../../support/api/policies';
|
||||
import { login, User } from '../../support/login/users';
|
||||
|
||||
describe('private labeling', () => {
|
||||
const orgPath = `/ui/console/org`;
|
||||
|
||||
[User.OrgOwner].forEach((user) => {
|
||||
describe(`as user "${user}"`, () => {
|
||||
let api: apiCallProperties;
|
||||
|
||||
beforeEach(() => {
|
||||
login(user);
|
||||
cy.visit(orgPath);
|
||||
// TODO: Why force?
|
||||
cy.contains('[data-e2e=policy-card]', 'Private Labeling').contains('button', 'Modify').click({ force: true }); // TODO: select data-e2e
|
||||
});
|
||||
|
||||
customize('white', user);
|
||||
customize('dark', user);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function customize(theme: string, user: User) {
|
||||
describe(`${theme} theme`, () => {
|
||||
beforeEach(() => {
|
||||
apiAuth().then((api) => {
|
||||
resetPolicy(api, Policy.Label);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('logo', () => {
|
||||
beforeEach('expand logo category', () => {
|
||||
cy.contains('[data-e2e=policy-category]', 'Logo').click(); // TODO: select data-e2e
|
||||
cy.fixture('logo.png').as('logo');
|
||||
});
|
||||
|
||||
it('should update a logo', () => {
|
||||
cy.get('[data-e2e=image-part-logo]')
|
||||
.find('input')
|
||||
.then(function (el) {
|
||||
const blob = Cypress.Blob.base64StringToBlob(this.logo, 'image/png');
|
||||
const file = new File([blob], 'images/logo.png', { type: 'image/png' });
|
||||
const list = new DataTransfer();
|
||||
|
||||
list.items.add(file);
|
||||
const myFileList = list.files;
|
||||
|
||||
el[0].files = myFileList;
|
||||
el[0].dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
});
|
||||
it('should delete a logo');
|
||||
});
|
||||
it('should update an icon');
|
||||
it('should delete an icon');
|
||||
it.skip('should update the background color', () => {
|
||||
cy.contains('[data-e2e=color]', 'Background Color').find('button').click(); // TODO: select data-e2e
|
||||
cy.get('color-editable-input').find('input').clear().type('#ae44dc');
|
||||
cy.get('[data-e2e=save-colors-button]').click();
|
||||
cy.get('[data-e2e=header-user-avatar]').click();
|
||||
cy.contains('Logout All Users').click(); // TODO: select data-e2e
|
||||
login(User.LoginPolicyUser, undefined, true, null, () => {
|
||||
cy.pause();
|
||||
});
|
||||
});
|
||||
it('should update the primary color');
|
||||
it('should update the warning color');
|
||||
it('should update the font color');
|
||||
it('should update the font style');
|
||||
it('should hide the loginname suffix');
|
||||
it('should show the loginname suffix');
|
||||
it('should hide the watermark');
|
||||
it('should show the watermark');
|
||||
it('should show the current configuration');
|
||||
it('should reset the policy');
|
||||
});
|
||||
}
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
15
e2e/cypress/support/api/apiauth.ts
Normal file
15
e2e/cypress/support/api/apiauth.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { login, User } from 'support/login/users'
|
||||
|
||||
export interface apiCallProperties {
|
||||
authHeader: string
|
||||
mgntBaseURL: string
|
||||
}
|
||||
|
||||
export function apiAuth(): Cypress.Chainable<apiCallProperties> {
|
||||
return login(User.IAMAdminUser, 'Password1!', false, true).then(token => {
|
||||
return <apiCallProperties>{
|
||||
authHeader: `Bearer ${token}`,
|
||||
mgntBaseURL: `/management/v1/`,
|
||||
}
|
||||
})
|
||||
}
|
@ -7,14 +7,14 @@ namespace Cypress {
|
||||
*
|
||||
* @example cy.consolelogin('hodor', 'hodor1234')
|
||||
*/
|
||||
/* consolelogin(username: string, password: string): void
|
||||
/* consolelogin(username: string, password: string): void
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('consolelogin', { prevSubject: false }, (username: string, password: string) => {
|
||||
|
||||
window.sessionStorage.removeItem("zitadel:access_token")
|
||||
cy.visit(Cypress.env('consoleUrl')).then(() => {
|
||||
cy.visit(Cypress.config('baseUrl')/ui/console).then(() => {
|
||||
// fill the fields and push button
|
||||
cy.get('#loginName').type(username, { log: false })
|
||||
cy.get('#submit-button').click()
|
20
e2e/cypress/support/e2e.ts
Normal file
20
e2e/cypress/support/e2e.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
219
e2e/cypress/support/login/users.ts
Normal file
219
e2e/cypress/support/login/users.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import { debug } from "console";
|
||||
|
||||
export enum User {
|
||||
OrgOwner = 'org_owner',
|
||||
OrgOwnerViewer = 'org_owner_viewer',
|
||||
OrgProjectCreator = 'org_project_creator',
|
||||
LoginPolicyUser = 'login_policy_user',
|
||||
PasswordComplexityUser = 'password_complexity_user',
|
||||
IAMAdminUser = 'zitadel-admin',
|
||||
}
|
||||
|
||||
export function login(
|
||||
user: User,
|
||||
pw?: string,
|
||||
force?: boolean,
|
||||
skipMFAChangePW?: boolean,
|
||||
onUsernameScreen?: () => void,
|
||||
onPasswordScreen?: () => void,
|
||||
onAuthenticated?: () => void,
|
||||
): Cypress.Chainable<string> {
|
||||
let creds = credentials(user, pw);
|
||||
|
||||
const loginUrl: string = '/ui/login';
|
||||
const issuerUrl: string = '/oauth/v2';
|
||||
const otherZitadelIdpInstance: boolean = Cypress.env('otherZitadelIdpInstance');
|
||||
|
||||
return cy.session(
|
||||
creds.username,
|
||||
() => {
|
||||
const cookies = new Map<string, string>();
|
||||
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${loginUrl}*`,
|
||||
times: 1,
|
||||
},
|
||||
(req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies);
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies);
|
||||
});
|
||||
},
|
||||
).as('login');
|
||||
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `${loginUrl}/loginname*`,
|
||||
times: 1,
|
||||
},
|
||||
(req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies);
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies);
|
||||
});
|
||||
},
|
||||
).as('loginName');
|
||||
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `${loginUrl}/password*`,
|
||||
times: 1,
|
||||
},
|
||||
(req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies);
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies);
|
||||
});
|
||||
},
|
||||
).as('password');
|
||||
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${loginUrl}/success*`,
|
||||
times: 1,
|
||||
},
|
||||
(req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies);
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies);
|
||||
});
|
||||
},
|
||||
).as('success');
|
||||
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${issuerUrl}/authorize/callback*`,
|
||||
times: 1,
|
||||
},
|
||||
(req) => {
|
||||
req.headers['cookie'] = requestCookies(cookies);
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies);
|
||||
});
|
||||
},
|
||||
).as('callback');
|
||||
|
||||
cy.intercept(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `${issuerUrl}/authorize*`,
|
||||
times: 1,
|
||||
},
|
||||
(req) => {
|
||||
req.continue((res) => {
|
||||
updateCookies(res.headers['set-cookie'] as string[], cookies);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
let userToken: string
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: `${issuerUrl}/token`,
|
||||
}, req => {
|
||||
req.continue(res => {
|
||||
userToken = res.body["access_token"]}
|
||||
)
|
||||
}).as('token')
|
||||
|
||||
cy.visit(loginUrl, { retryOnNetworkFailure: true });
|
||||
|
||||
otherZitadelIdpInstance && cy.wait('@login');
|
||||
onUsernameScreen ? onUsernameScreen() : null;
|
||||
cy.get('#loginName').type(creds.username);
|
||||
cy.get('#submit-button').click();
|
||||
|
||||
otherZitadelIdpInstance && cy.wait('@loginName');
|
||||
onPasswordScreen ? onPasswordScreen() : null;
|
||||
cy.get('#password').type(creds.password);
|
||||
cy.get('#submit-button').click();
|
||||
|
||||
cy.wait('@password').then((interception) => {
|
||||
if (interception.response.body.indexOf('Multifactor Setup') === -1){
|
||||
return
|
||||
}
|
||||
|
||||
cy.contains('button', 'skip').click()
|
||||
cy.get('#change-old-password').type(creds.password)
|
||||
cy.get('#change-new-password').type(creds.password)
|
||||
cy.get('#change-password-confirmation').type(creds.password)
|
||||
cy.contains('button', 'next').click()
|
||||
cy.contains('button', 'next').click()
|
||||
})
|
||||
|
||||
cy.wait('@token').then(() => {
|
||||
cy.task('safetoken', {key: creds.username, token: userToken})
|
||||
})
|
||||
|
||||
onAuthenticated ? onAuthenticated() : null;
|
||||
|
||||
otherZitadelIdpInstance && cy.wait('@callback');
|
||||
|
||||
cy.location('pathname', { timeout: 5 * 1000 }).should('eq', '/ui/console/');
|
||||
},
|
||||
{
|
||||
validate: () => {
|
||||
if (force) {
|
||||
throw new Error('clear session');
|
||||
}
|
||||
},
|
||||
},
|
||||
).then(() => {
|
||||
return cy.task('loadtoken', {key: creds.username})
|
||||
});
|
||||
}
|
||||
|
||||
export function loginname(withoutDomain: string, org?: string): string {
|
||||
return `${withoutDomain}@${org}.${host(Cypress.config('baseUrl'))}`;
|
||||
}
|
||||
|
||||
function credentials(user: User, pw?: string) {
|
||||
|
||||
// TODO: ugly
|
||||
const woDomain = user == User.IAMAdminUser ? User.IAMAdminUser : `${user}_user_name`
|
||||
const org = Cypress.env('ORGANIZATION') ? Cypress.env('ORGANIZATION') : 'zitadel'
|
||||
|
||||
return {
|
||||
username: loginname(woDomain, org),
|
||||
password: pw ? pw : Cypress.env(`${user}_password`),
|
||||
};
|
||||
}
|
||||
|
||||
function updateCookies(newCookies: string[] | undefined, currentCookies: Map<string, string>) {
|
||||
if (newCookies === undefined) {
|
||||
return;
|
||||
}
|
||||
newCookies.forEach((cs) => {
|
||||
cs.split('; ').forEach((cookie) => {
|
||||
const idx = cookie.indexOf('=');
|
||||
currentCookies.set(cookie.substring(0, idx), cookie.substring(idx + 1));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function requestCookies(currentCookies: Map<string, string>): string[] {
|
||||
let list: Array<string> = [];
|
||||
currentCookies.forEach((val, key) => {
|
||||
list.push(key + '=' + val);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
export function host(url: string): string {
|
||||
return stripPort(stripProtocol(url));
|
||||
}
|
||||
|
||||
function stripPort(s: string): string {
|
||||
const idx = s.indexOf(':');
|
||||
return idx === -1 ? s : s.substring(0, idx);
|
||||
}
|
||||
|
||||
function stripProtocol(url: string): string {
|
||||
return url.replace('http://', '').replace('https://', '');
|
||||
}
|
9
e2e/cypress/tsconfig.json
Normal file
9
e2e/cypress/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
63
e2e/docker-compose.yaml
Normal file
63
e2e/docker-compose.yaml
Normal file
@ -0,0 +1,63 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
zitadel:
|
||||
restart: 'always'
|
||||
networks:
|
||||
- 'zitadel'
|
||||
image: '${ZITADEL_IMAGE:-ghcr.io/zitadel/zitadel:latest}'
|
||||
command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled'
|
||||
environment:
|
||||
ZITADEL_DATABASE_COCKROACH_HOST: db
|
||||
ZITADEL_EXTERNALSECURE: false
|
||||
ZITADEL_EXTERNALDOMAIN: zitadel
|
||||
ZITADEL_FIRSTINSTANCE_CUSTOMDOMAIN: zitadel
|
||||
|
||||
depends_on:
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
ports:
|
||||
- '8080:8080'
|
||||
|
||||
db:
|
||||
restart: 'always'
|
||||
networks:
|
||||
- 'zitadel'
|
||||
image: 'cockroachdb/cockroach:v22.1.0'
|
||||
command: 'start-single-node --insecure'
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"]
|
||||
interval: '10s'
|
||||
timeout: '30s'
|
||||
retries: 5
|
||||
start_period: '20s'
|
||||
ports:
|
||||
- '9090:8080'
|
||||
- '26257:26257'
|
||||
|
||||
npm-install:
|
||||
image: node:18-alpine3.15
|
||||
working_dir: /e2e
|
||||
volumes:
|
||||
- .:/e2e
|
||||
command: "npm ci"
|
||||
|
||||
e2e:
|
||||
image: cypress/included:10.3.0
|
||||
environment:
|
||||
CYPRESS_BASE_URL: http://zitadel:8080
|
||||
depends_on:
|
||||
zitadel:
|
||||
condition: 'service_started'
|
||||
db:
|
||||
condition: 'service_healthy'
|
||||
npm-install:
|
||||
condition: 'service_completed_successfully'
|
||||
working_dir: /e2e
|
||||
volumes:
|
||||
- .:/e2e
|
||||
networks:
|
||||
- zitadel
|
||||
|
||||
networks:
|
||||
zitadel:
|
4704
e2e/package-lock.json
generated
Normal file
4704
e2e/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
e2e/package.json
Normal file
15
e2e/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "zitadel-e2e",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"start": "npx cypress open",
|
||||
"run": "npx cypress run"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"mochawesome": "^7.1.3",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
9
e2e/tsconfig.json
Normal file
9
e2e/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user