finish local frontend dev

This commit is contained in:
Elio Bischof
2022-08-24 16:45:40 +02:00
parent 957ec3272f
commit 128392cc5e
18 changed files with 2631 additions and 175 deletions

View File

@@ -120,43 +120,68 @@ Make sure to use the following configurations:
### Console
Change to the console directory
Run the database and the latests backend locally.
```bash
# Change to the console directory
cd ./console
```
Run the database and the backend locally
```bash
# You just need the db and the zitadel services to develop the console against.
docker compose --file ../e2e/docker-compose.yaml up --detach db zitadel
```
Console loads its environment from the file console/src/assets/environment.json.
Load it from your local target system.
When the backend is ready, you have the latest zitadel exposed at http://localhost:8080.
You can now run a local development server with live code reloading at http://localhost:4200.
To allow console access via http://localhost:4200, you have to configure the ZITADEL backend.
1. Navigate to http://localhost:8080/ui/console/projects.
2. When propted, login with *zitadel-admin@zitadel.localhost* and *Password1!*.
3. Select the *ZITADEL* project.
3. Select the *Console* application.
4. Select *Redirect Settings*
5. Add *http://localhost:4200/auth/callback* to the *Redirect URIs*
6. Add *http://localhost:4200/signedout* to the *Post Logout URIs*
7. Select the *Save* button
You can run the local console development server now.
```bash
# Console loads its target environment from the file console/src/assets/environment.json.
# Load it from the backend.
curl -O ./src/assets/environment.json http://localhost:8080/ui/console/assets/environment.json
# Generate source files from Protos
npm run generate
# Install npm dependencies
npm install
# Start the server
npm start
```
To generate source files from protos, run the following command
```
DOCKER_BUILDKIT=1 docker build -f ../build/console/Dockerfile . -t zitadel-npm-base --target npm-copy -o internal/api/ui/console/static
```
To run the console locally, run `npm install` and then `npm start` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.
You can now also run end-to-end tests interactively.
Open a new shell and change to the e2e directory
Navigate to http://localhost:4200/.
Make some changes to the source code and see how the browser is automatically reloaded.
After making changes to the code, you should run the end-to-end-tests.
Open another shell.
```bash
# Change to the e2e directory
cd ./e2e
# Install npm dependencies
npm install
# Run all tests in a headless browser
npm run e2e:dev
```
Start cypress and point it to your local dev server:
You can also open the test suite interactively for fast success feedback on specific tests.
```bash
CYPRESS_BASE_URL=http://localhost:4200 npm start
# Run all tests in a headless browser
npm run open:dev
```
### API Definitions

View File

@@ -6,7 +6,8 @@
"start": "ng serve",
"build": "ng build",
"prodbuild": "ng build --configuration production --base-href=/ui/console/",
"lint": "ng lint && stylelint './src/**/*.scss' --syntax scss"
"lint": "ng lint && stylelint './src/**/*.scss' --syntax scss",
"generate": "DOCKER_BUILDKIT=1 docker build -f ../build/console/Dockerfile .. --target npm-copy -o .."
},
"private": true,
"dependencies": {

View File

@@ -1,7 +1,7 @@
<div class="max-width-container">
<div class="home-wrapper enlarged-container">
<div class="header">
<h1 class="title">{{ 'HOME.WELCOME' | translate }}</h1>
<h1 class="title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
</div>
<cnsl-shortcuts></cnsl-shortcuts>

View File

@@ -17,11 +17,12 @@ export default defineConfig({
defaultCommandTimeout: 10000,
env: {
ORGANIZATION: process.env.CYPRESS_ORGANIZATION || 'zitadel'
ORGANIZATION: process.env.CYPRESS_ORGANIZATION || 'zitadel',
BACKEND_URL: process.env.CYPRESS_BACKEND_URL || baseUrl().replace("/ui/console", "")
},
e2e: {
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:8080',
baseUrl: baseUrl(),
experimentalSessionAndOrigin: true,
setupNodeEvents(on, config) {
@@ -39,3 +40,7 @@ export default defineConfig({
},
},
});
function baseUrl(){
return process.env.CYPRESS_BASE_URL || 'http://localhost:8080/ui/console'
}

View File

@@ -10,7 +10,7 @@ describe('applications', () => {
apiAuth().then(api => {
ensureProjectExists(api, testProjectName).then(projectID => {
ensureProjectResourceDoesntExist(api, projectID, Apps, testAppName).then(() => {
cy.visit(`/ui/console/projects/${projectID}`)
cy.visit(`/projects/${projectID}`)
})
})
})

View File

@@ -6,7 +6,7 @@ import {
import { loginname } from "../../support/login/users";
describe.skip("humans", () => {
const humansPath = `/ui/console/users?type=human`;
const humansPath = `/users?type=human`;
const testHumanUserNameAdd = "e2ehumanusernameadd";
const testHumanUserNameRemove = "e2ehumanusernameremove";

View File

@@ -6,7 +6,7 @@ import {
import { loginname } from "../../support/login/users";
describe.skip("machines", () => {
const machinesPath = `/ui/console/users?type=machine`;
const machinesPath = `/users?type=machine`;
const testMachineUserNameAdd = "e2emachineusernameadd";
const testMachineUserNameRemove = "e2emachineusernameremove";

View File

@@ -24,7 +24,7 @@ describe.skip('permissions', () => {
beforeEach(()=> {
apiAuth().then((api)=> {
ensureProjectResourceDoesntExist(api, projectId, Roles, testRoleName)
cy.visit(`/ui/console/projects/${projectId}?id=roles`)
cy.visit(`/projects/${projectId}?id=roles`)
})
})

View File

@@ -14,7 +14,7 @@ describe("projects", () => {
apiAuth().then((api) => {
ensureProjectDoesntExist(api, testProjectNameCreate);
});
cy.visit(`/ui/console/projects`);
cy.visit(`/projects`);
});
it("should add a project", () => {
@@ -33,7 +33,7 @@ describe("projects", () => {
apiAuth().then((api) => {
ensureProjectExists(api, testProjectNameDeleteList);
});
cy.visit(`/ui/console/projects`);
cy.visit(`/projects`);
});
it("removes the project", () => {
@@ -57,7 +57,7 @@ describe("projects", () => {
apiAuth().then((api) => {
ensureProjectExists(api, testProjectNameDeleteGrid);
});
cy.visit(`/ui/console/projects`);
cy.visit(`/projects`);
});
it("removes the project", () => {

View File

@@ -4,7 +4,7 @@ import { login, User } from "../../support/login/users";
describe("login policy", ()=> {
const orgPath = `/ui/console/org`
const orgPath = `/org`
;[User.OrgOwner].forEach(user => {

View File

@@ -2,7 +2,7 @@ import { login, User } from "../../support/login/users";
describe("password complexity", ()=> {
const orgPath = `/ui/console/org`
const orgPath = `/org`
const testProjectName = 'e2eproject'
;[User.OrgOwner].forEach(user => {

View File

@@ -3,7 +3,7 @@ import { Policy, resetPolicy } from '../../support/api/policies';
import { login, User } from '../../support/login/users';
describe('private labeling', () => {
const orgPath = `/ui/console/org`;
const orgPath = `/org`;
[User.OrgOwner].forEach((user) => {
describe(`as user "${user}"`, () => {

View File

@@ -9,7 +9,7 @@ export function apiAuth(): Cypress.Chainable<apiCallProperties> {
return login(User.IAMAdminUser, 'Password1!', false, true).then(token => {
return <apiCallProperties>{
authHeader: `Bearer ${token}`,
mgntBaseURL: `/management/v1/`,
mgntBaseURL: `${Cypress.env("BACKEND_URL")}/management/v1/`,
}
})
}

View File

@@ -22,95 +22,20 @@ export function login(
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);
});
},
);
cy.intercept({
times: 6
}, req => {
req.headers['cookie'] = requestCookies(cookies);
req.continue((res) => {
updateCookies(res.headers['set-cookie'] as string[], cookies);
});
})
let userToken: string
cy.intercept({
@@ -122,14 +47,18 @@ export function login(
)
}).as('token')
cy.intercept({
method: 'POST',
url: `${loginUrl}/password*`,
times: 1,
}).as('password');
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();
@@ -153,9 +82,7 @@ export function login(
onAuthenticated ? onAuthenticated() : null;
otherZitadelIdpInstance && cy.wait('@callback');
cy.location('pathname', { timeout: 5 * 1000 }).should('eq', '/ui/console/');
cy.get("[data-e2e=authenticated-welcome]");
},
{
validate: () => {

View File

@@ -2,8 +2,8 @@
"compilerOptions": {
"baseUrl": "./",
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
"lib": ["es5", "dom", "es2015"],
"types": ["cypress", "node"]
},
"include": ["**/*.ts"]
}

2581
e2e/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,20 @@
"name": "zitadel-e2e",
"version": "0.0.0",
"scripts": {
"start": "npx cypress open",
"run": "npx cypress run"
},
"open": "npx cypress open",
"e2e": "npx cypress run",
"open:dev": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run open",
"e2e:dev": "CYPRESS_BASE_URL=http://localhost:4200 CYPRESS_BACKEND_URL=http://localhost:8080 npm run e2e"
},
"private": true,
"dependencies": {
"debug": "^4.3.4",
"jsonwebtoken": "^8.5.1",
"mochawesome": "^7.1.3",
"typescript": "^4.7.4"
},
"devDependencies": {
"@types/node": "^18.7.13",
"cypress": "^10.3.0"
}
}

View File

@@ -1,9 +0,0 @@
{
"compilerOptions": {
"baseUrl": "./",
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
},
"include": ["**/*.ts"]
}