Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement e2e tests workflow #227

Open
wants to merge 10 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,52 @@ jobs:
VC test suite, ./packages/vc-test-suite/coverage/coverage-summary.json
UNTP Playground, ./packages/untp-playground/coverage/coverage-summary.json

- name: Start E2E docker compose
run: SEEDING=true docker compose -f docker-compose.e2e.yml up -d
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will GitHub cache the docker image between each run? If so, we need to build the image so the new functionality is present in the containers.

Copy link
Member Author

@huynguyen-hl huynguyen-hl Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that GitHub Actions has the capability to cache all Docker images defined in the docker-compose file? If so, I'm not sure about that. I believe it can cache a specific standalone Docker image, but I'm not sure if it applies to all images in a docker-compose setup.

If your idea isn't what I thought, please share more details. Thank you!


- name: Ensure IDR Service Readiness and Health
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @huynguyen-hl, is there a reason why we aren't using the built-in docker-compose health check functionality?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see, when we run docker compose up, it pulls all the Docker images defined in the compose file and starts the services. The terminal/console will return the cursor to us, allowing us to run another command, regardless of whether health checks are set up for each service. However, in the background, docker compose up continues running to initialize all services.

At this point, the next stepβ€”running the E2E testsβ€”will be triggered. But how do the E2E tests determine whether the services have started successfully and passed their internal health checks as defined in the Docker Compose file? The docker compose up command does not wait until all services are fully running and healthy before returning control to us. So how can we ensure that all services are ready before running the E2E tests?

run: |
HEALTH_ENDPOINT="http://localhost:3000/health-check"
MAX_ATTEMPTS=10
RETRY_INTERVAL=6
ATTEMPT_COUNTER=0

echo "Waiting for IDR service to become ready..."

while true; do
if curl --output /dev/null --silent --fail "$HEALTH_ENDPOINT"; then
echo "IDR service is reachable."
break
fi

ATTEMPT_COUNTER=$((ATTEMPT_COUNTER + 1))

if [[ "$ATTEMPT_COUNTER" -eq "$MAX_ATTEMPTS" ]]; then
echo "Max attempts ($MAX_ATTEMPTS) reached. Service failed to start."
exit 1
fi

echo "Attempt $ATTEMPT_COUNTER/$MAX_ATTEMPTS failed. Retrying in $RETRY_INTERVAL seconds..."
sleep "$RETRY_INTERVAL"
done

echo "Checking IDR service health status..."

HEALTH_STATUS=$(curl -s "$HEALTH_ENDPOINT" | jq -r '.status')

if [[ "$HEALTH_STATUS" != "OK" ]]; then
echo "Health check failed. Expected status 'OK', but got '$HEALTH_STATUS'."
exit 1
fi

echo "IDR service health check passed successfully."

- name: Run E2E tests
run: yarn test:run-cypress

- name: Stop docker compose
run: docker compose -f docker-compose.e2e.yml down

build_docs:
runs-on: ubuntu-latest
steps:
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,52 @@ jobs:
- name: Run tests
run: yarn test

- name: Start E2E docker compose
run: SEEDING=true docker compose -f docker-compose.e2e.yml up -d
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


- name: Ensure IDR Service Readiness and Health
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

run: |
HEALTH_ENDPOINT="http://localhost:3000/health-check"
MAX_ATTEMPTS=10
RETRY_INTERVAL=6
ATTEMPT_COUNTER=0

echo "Waiting for IDR service to become ready..."

while true; do
if curl --output /dev/null --silent --fail "$HEALTH_ENDPOINT"; then
echo "IDR service is reachable."
break
fi

ATTEMPT_COUNTER=$((ATTEMPT_COUNTER + 1))

if [[ "$ATTEMPT_COUNTER" -eq "$MAX_ATTEMPTS" ]]; then
echo "Max attempts ($MAX_ATTEMPTS) reached. Service failed to start."
exit 1
fi

echo "Attempt $ATTEMPT_COUNTER/$MAX_ATTEMPTS failed. Retrying in $RETRY_INTERVAL seconds..."
sleep "$RETRY_INTERVAL"
done

echo "Checking IDR service health status..."

HEALTH_STATUS=$(curl -s "$HEALTH_ENDPOINT" | jq -r '.status')

if [[ "$HEALTH_STATUS" != "OK" ]]; then
echo "Health check failed. Expected status 'OK', but got '$HEALTH_STATUS'."
exit 1
fi

echo "IDR service health check passed successfully."

- name: Run E2E tests
run: yarn test:run-cypress

- name: Stop docker compose
run: docker compose -f docker-compose.e2e.yml down

build_docs:
runs-on: ubuntu-latest
steps:
Expand Down
66 changes: 46 additions & 20 deletions e2e/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,21 @@ import { defineConfig } from 'cypress';
import fs from 'fs';
import path from 'path';
import util from 'util';
import { Client, ClientOptions } from 'minio';

const execPromise = util.promisify(exec);
export default defineConfig({
env: {
idrBucketName: process.env.OBJECT_STORAGE_BUCKET_NAME || 'idr-bucket-1',
idrGS1Prefix: process.env.OBJECT_STORAGE_GS1_PREFIX || 'gs1',
idrMinioConfig: {
endPoint: process.env.APP_ENDPOINT || 'localhost',
port: parseInt(process.env.OBJECT_STORAGE_PORT || '9000', 10),
useSSL: process.env.OBJECT_STORAGE_USE_SSL === 'true',
accessKey: process.env.OBJECT_STORAGE_ACCESS_KEY || 'minioadmin',
secretKey: process.env.OBJECT_STORAGE_SECRET_KEY || 'minioadmin',
},
},
e2e: {
baseUrl: 'http://localhost:3003', // Replace with your application's base URL
supportFile: 'cypress/support/e2e.ts',
Expand All @@ -16,7 +28,7 @@ export default defineConfig({
runMode: 2, // Retries in headless mode
openMode: 0, // No retries in interactive mode
},
defaultCommandTimeout: 4000,
defaultCommandTimeout: 10000,
defaultBrowser: 'chrome',
setupNodeEvents(on) {
on('task', {
Expand All @@ -34,28 +46,42 @@ export default defineConfig({
throw error;
}
},
resetData(file?: string) {
const targetDir = path.resolve(
process.cwd(),
`../minio_data/identity-resolver-service-object-store/data-test/idr-bucket-1/gs1${file ? `/${file}` : ''}`,
);

if (fs.existsSync(targetDir)) {
console.log(`Found folder: ${targetDir}`);
console.log('Deleting folder...');

fs.rmdirSync(targetDir, { recursive: true });
async clearObjectStore({ bucketName, prefix, minioConfig }: { bucketName: string; prefix?: string; minioConfig: ClientOptions }) {
try {
if (!bucketName) {
return {
success: false,
message: 'Bucket name is required.',
}
}

if (!fs.existsSync(targetDir)) {
console.log('Folder deleted successfully.');
return { success: true };
} else {
console.log('Failed to delete the folder.');
return { success: false };
const minioClient = new Client(minioConfig);
const bucketExists = await minioClient.bucketExists(bucketName);
if (!bucketExists) {
return {
success: false,
message: `Bucket ${bucketName} does not exist.`,
};
}

const objects: string[] = [];
const bucketStream = minioClient.listObjectsV2(bucketName, prefix, true); // true for recursive

// Collect all object names
await new Promise<void>((resolve, reject) => {
bucketStream.on('data', (obj) => obj.name && objects.push(obj.name));
bucketStream.on('error', (err) => reject(err));
bucketStream.on('end', () => resolve());
});

if (objects.length > 0) {
await minioClient.removeObjects(bucketName, objects);
}
} else {
console.log(`Folder not found: ${targetDir}`);

return { success: true };
} catch (error) {
console.log(error);
throw error;
}
},
async runUntpTest({ type, version, testData }) {
Expand Down
6 changes: 5 additions & 1 deletion e2e/cypress/support/commands/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
after(() => {
cy.task('resetData');
cy.task('clearObjectStore', {
bucketName: Cypress.env('idrBucketName'),
prefix: Cypress.env('idrGS1Prefix'),
minioConfig: Cypress.env('idrMinioConfig'),
});
Cypress.env('lastCredential', undefined);
});
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"test:vc-test-suite": "cd packages/vc-test-suite && yarn run test",
"test:untp-playground": "cd packages/untp-playground && yarn run test",
"test:integration:untp-test-suite": "cd packages/untp-test-suite && yarn run test:integration",
"test:run-cypress": "cd e2e && yarn cypress run",
"test:open-cypress": "cd e2e && yarn cypress open",
"test:run-cypress": "cd e2e && cypress run",
"test:open-cypress": "cd e2e && cypress open",
"storybook:components": "cd packages/components && yarn run storybook",
"storybook:mock-app": "cd packages/mock-app && yarn run storybook",
"untp": "node ./packages/untp-test-suite/build/interfaces/cli/cli.js",
Expand All @@ -45,6 +45,7 @@
"@types/uuid": "^9.0.7",
"cross-env": "^7.0.3",
"cypress": "^13.16.1",
"minio": "^8.0.4",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^8.0.0",
"jest": "29.7.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/untp-playground/.dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
infra
infra
node_modules
2 changes: 1 addition & 1 deletion packages/untp-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"clsx": "^2.1.1",
"jsonld": "^8.3.3",
"jwt-decode": "^4.0.0",
"next": "15.0.3",
"lucide-react": "^0.475.0",
"next": "^15.0.3",
"react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106",
"react-dropzone": "^14.3.5",
Expand Down
Loading