mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-30 06:39:25 +00:00
finish local frontend dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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`)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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}"`, () => {
|
||||
|
||||
@@ -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/`,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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
2581
e2e/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user