Build and test
Every module of the repository, whatever its language, is an Nx project. You build and test each one with the same commands from the repository root. This page covers Nx itself, then what is specific to TypeScript, Go, and Java, the container images, the Storybooks and screenshot tests, and the E2E suites. Set up the tools first: Getting started.
Nx basics
Section titled “Nx basics”Run everything from the repository root with pnpm exec nx:
# one target of one projectpnpm exec nx run api-diff:build
# several targets of several projects, in dependency orderpnpm exec nx run-many -t build test -p api-diff,api-processor
# what your branch changed, compared with developpnpm exec nx affected -t build test- Projects.
pnpm exec nx show projectslists them;pnpm exec nx show project <name>prints the targets of one, and--webopens it in the browser.pnpm exec nx graphshows the dependency graph. - Project names. TypeScript projects are named in the
nx.namefield of theirpackage.json, for exampleui-portalforfrontend/ui/packages/portal. Go projects take the module directory name (portal-backend). The Maven plugin names Java projects<groupId>:<artifactId>; the parent project is renamedjava-task-consumer. - Targets. A TypeScript project’s targets are the scripts of its
package.json. Go targets come from the@nx-go/nx-goplugin, Java targets from@nx/maven. - Dependencies.
buildandtestbuild the in-repo dependencies first (dependsOn: ["^build"]innx.json), sonx run api-processor:testbuildsapi-unifier,api-diff, and the rest before it runs the tests. - Cache.
build,test,lint:check,type-check, andscreenshot-testare cached. A repeated run with the same inputs restores the output and the log instead of running again.--skip-nx-cacheforces a run;pnpm exec nx resetclears the cache and stops the daemon. - Affected.
nx affectedcompares withdevelop(defaultBaseinnx.json). CI runs the same command against the merge base of the pull request. Use--base=<ref>to compare with something else. - Tags. Every Go module has the tag
lang:go, and the Java projectslang:javaormaven:<groupId>. Select by tag with-p tag:lang:goor exclude with--exclude=tag:lang:go.
The root package.json scripts pnpm build and pnpm test run pnpm -r build and pnpm -r test over the
workspace without Nx: no dependency order, no cache. Prefer the Nx commands.
TypeScript
Section titled “TypeScript”The pnpm workspace in pnpm-workspace.yaml holds the frontend libraries, the UI, the
builder (build-task-consumer), the Playwright tests, the screenshot test environment, and the sites. One
pnpm install at the root installs all of them.
| Target | What it does | Examples |
|---|---|---|
build |
compiles to the project’s dist/ |
nx run api-processor:build, nx run-many -t build -p ui-portal,ui-agents |
test |
runs the unit tests (Jest) | nx run api-diff:test, nx run ui-shared:test |
lint:check |
runs ESLint without fixing | nx run ui-portal:lint:check |
lint:fix |
runs ESLint and fixes what it can | nx run api-unifier:lint:fix |
type-check |
runs tsc without output, where the project has it |
nx run apispec-view:type-check |
- Dependencies between workspace projects use
workspace:ranges. A change inapi-unifierreachesapi-processoron the next build, without publishing anything. - The workspace uses
nodeLinker: isolated: each project sees only the dependencies it declares. An import of an undeclared package fails to resolve even when another project has it. Add the dependency to the project’spackage.jsonand runpnpm installat the root. - Run a script of one project without Nx with
pnpm --filter <package name> run <script>, for examplepnpm --filter @netcracker/qubership-apihub-api-diff run test:compatibility-suites. Nx does not cache it.
The six Go modules under backend/ form one workspace in go.work: portal-backend,
api-linter-service, agents-backend, agent, test-service, and the shared library commons-go. All of them use
the Go version in backend/.go-version.
pnpm exec nx run portal-backend:buildpnpm exec nx run-many -t build test -p tag:lang:gobuildwrites the binary todist/backend/<module>(.exeon Windows);testrunsgo test ./...in the module.gocommands work too from a module directory, for examplego test ./service/...inbackend/portal-backend. They use the workspace, so a change incommons-gois visible at once.- Keep
go.modfiles in sync with the workspace. The images build each module alone withGOWORK=off, so everygo.modmust already require the versions the workspace resolves. After adding or upgrading a dependency, rungo work syncat the root and commit the changedgo.modandgo.sumfiles.tools/ci/go-work-sync-check.shis the check CI runs; run it locally in Git Bash to see the same result. - Change the Go version in one place. Edit
backend/.go-version, thegoline ofgo.workand of everygo.mod, and thegolangbase image of the Dockerfiles.tools/ci/go-version-check.shfails CI when they disagree.
backend/java-task-consumer is a Maven reactor with two modules, jdiff-engine and java-task-consumer. The root
pom.xml aggregates it, so @nx/maven sees it.
# the whole reactor: mvn --batch-mode verify, with all testspnpm exec nx run java-task-consumer:build
# one Maven module through the inferred targetspnpm exec nx run com.netcracker.qubership.apihub:jdiff-engine:testjava-task-consumer:buildis the target CI runs. It writes the shaded jar tobackend/java-task-consumer/java-task-consumer/target/.- Plain Maven works too:
mvn verifyinbackend/java-task-consumer. - The Pact test rewrites the tracked file
backend/java-task-consumer/pacts/java-task-consumer-libraries-backend.jsonon every run, with new timestamps only. Restore it withgit checkout -- backend/java-task-consumer/pactsunless you changed the contract. - Windows: the inferred Maven targets need
MAVEN_HOMEset to the Maven installation directory. Without it they fail at once withhad no output from Maven.java-task-consumer:buildcallsmvnfromPATHand does not need it.
Images
Section titled “Images”CI builds and publishes the images; you build one locally only to run it in Compose or kind (see
Local run). The commands below match what images.yml does.
They use podman; docker takes the same arguments.
| Image | Build first | Image build (from the repository root) |
|---|---|---|
qubership-apihub-backend |
nothing | podman build -f backend/portal-backend/Dockerfile backend |
qubership-api-linter-service |
nothing | podman build -f backend/api-linter-service/Dockerfile backend |
qubership-apihub-agents-backend |
nothing | podman build -f backend/agents-backend/Dockerfile backend |
qubership-apihub-agent |
nothing | podman build -f backend/agent/Dockerfile backend/agent |
qubership-apihub-test-service |
nothing | podman build -f backend/test-service/Dockerfile backend/test-service |
qubership-apihub-ui |
pnpm exec nx run-many -t build -p ui-portal,ui-agents |
podman build -f frontend/ui/Dockerfile . |
qubership-apihub-build-task-consumer |
pnpm exec nx run build-task-consumer:build, then pnpm deploy --filter=./frontend/build-task-consumer --prod .deploy/build-task-consumer |
podman build -f frontend/build-task-consumer/Dockerfile . |
qubership-apihub-java-task-consumer |
pnpm exec nx run java-task-consumer:build |
podman build backend/java-task-consumer |
Add -t <name>:<tag> to name the image, for example -t ghcr.io/netcracker/qubership-apihub-backend:local.
- The Go images compile inside the image with
GOWORK=off. The service images usebackendas the build context because they copycommons-gonext to the service. - The UI and builder images copy what the workspace build wrote on the host, so build first.
pnpm deploywrites a productionnode_modulesfor the builder into.deploy/, which Git ignores.
Storybooks
Section titled “Storybooks”Six components have a Storybook, which CI publishes to GitHub Pages. Start one locally with its dev server:
| Component | Dev server | Static build (build:showcase) |
|---|---|---|
api-doc-viewer |
pnpm exec nx run api-doc-viewer:development:storybook |
frontend/api-doc-viewer/dist-showcase |
apispec-view |
pnpm exec nx run apispec-view:development:storybook |
frontend/apispec-view/dist-showcase |
class-view |
pnpm --filter @netcracker/qubership-apihub-class-view run development:local-server |
frontend/class-view/dist-showcase |
graphapi |
pnpm exec nx run graphapi:storybook |
frontend/graphapi/dist-showcase |
rest-playground |
pnpm exec nx run rest-playground:storybook |
frontend/rest-playground/dist-showcase |
ui |
pnpm exec nx run ui-shared:storybook |
frontend/ui/dist-showcase |
pnpm exec nx run-many -t build:showcase -p <project> builds the static Storybook into the folder in the last column.
Use run-many: CI does the same, because Nx can read nx run <project>:build:showcase as the target build with the
configuration showcase. The list of published Storybooks is
tools/ci/storybooks.json.
Screenshot tests
Section titled “Screenshot tests”api-doc-viewer, apispec-view, and class-view have screenshot tests. Each run builds the Storybook, serves it,
opens every story in Chrome, and compares the result with the reference images committed next to the tests. Chrome
runs in a container, so the pixels match CI on any operating system.
pnpm exec nx run class-view:screenshot-test- A container engine is required. The test environment
(
tools/jest-chrome-in-docker-environment) starts Chrome with thepodmancommand. SetDOCKER_BINARY=dockerto use Docker instead. On Windows, run Podman Desktop with its WSL machine. - Chrome in the container must reach the host. The environment looks up the host address from inside the
container (
host.docker.internal, then the container gateway) and checks it on the Storybook port. When none answers, the run stops inglobalSetupwithCould not resolve IP address of host machine. - Update the reference images after an intended visual change with the
regenerate-screenshotsscript, for examplepnpm --filter @netcracker/qubership-apihub-class-view run regenerate-screenshots. Commit the new images with the change and look at each one before you do. - When a screenshot test fails in CI, the run uploads the diff images as the
screenshot-diffs-<project>artifact.
E2E tests
Section titled “E2E tests”The E2E suites run against a running product: CI stands it up on kind for every pull request (see CI).
To run them locally, start the product first, as described in Local run. The default local
address is http://localhost:8081.
Newman (Postman collections)
Section titled “Newman (Postman collections)”The collections are in tests/postman-collections/e2e/. The environment
local.postman_environment.json points
at localhost:8081 and reads the administrator credentials from placeholders. Pass the real values with --env-var:
npm install -g newman@6.2.3newman run tests/postman-collections/e2e/1_1_Smoke_Portal.postman_collection.json \ -e tests/postman-collections/environment/local.postman_environment.json \ --env-var "Username=<admin email>" --env-var "User_email=<admin email>" \ --env-var "Password=<admin password>" --env-var "at-api-key=<access token>"The administrator and the access token are the APIHUB_ADMIN_EMAIL, APIHUB_ADMIN_PASSWORD, and
APIHUB_ACCESS_TOKEN values of your deployment. CI runs the collections in the order 1_1, 1_2 (with the agent
only), 1_3, 2_1, 3_1, 3_2, 4.
Playwright
Section titled “Playwright”The Playwright project is tests/ui-tests, part of the pnpm workspace; pnpm install at the root installed it.
cd tests/ui-testspnpm exec playwright install chromiumBASE_URL=http://localhost:8081 TEST_USER_PASSWORD=<password> pnpm exec playwright test --project=Portal- CI runs
--project=Portal --grep-invert=@flaky. - A
.envfile intests/ui-testscan holdBASE_URL,TEST_USER_PASSWORD, and the other variables. Thetests/ui-testsREADME lists them, with the extra settings for a local or Compose stack. pnpm exec playwright test --uiopens the interactive runner;pnpm exec playwright show-reportopens the last report. Debugging has more.
Linting outside Nx
Section titled “Linting outside Nx”Two linters run in CI on every pull request but not through Nx:
- super-linter over the changed files, with the configurations in
.github/linters/; - the link checker (lychee) over the Markdown files.
For Markdown, npx markdownlint-cli --config .github/linters/.markdownlint.json <files> gives the same result locally.