Skip to content

Commit

Permalink
try building test image
Browse files Browse the repository at this point in the history
try building test image

build test docker image

build test docker image

build test docker image

build test docker image

build test docker image

build test docker image

build test docker image

run test image

run test image

run test image

run test image

run test image

run test image

run test image

run test image

run test image

run test image

run test image

run test

run test

fix test

fix test

fix test

fix test

fix test

fix test

fix test

fix test

buildId and customId test

readme

readme

remove container images after run
  • Loading branch information
kb-kerem committed Apr 18, 2024
1 parent 5be06b3 commit ba6f21d
Show file tree
Hide file tree
Showing 11 changed files with 527 additions and 51 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/java-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
paths:
- 'visual-java/**'
- 'tests/**'
- .github/workflows/java-build.yml
pull_request:
paths:
Expand All @@ -27,3 +28,26 @@ jobs:
cache: 'maven'
- name: Build with Maven
run: mvn -B package --file pom.xml

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push to local registry
uses: docker/build-push-action@v5
with:
context: '{{defaultContext}}:visual-java'
tags: saucelabs/visual-java
file: Dockerfile
load: true
- name: Run the integration tests
run: |
npm ci
npm run test
working-directory: tests
env:
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
CONTAINER_IMAGE_NAME: saucelabs/visual-java
36 changes: 36 additions & 0 deletions tests/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.cjs'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',

// Allow unused vars that start with _
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
],
},
};
27 changes: 27 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Running the tests locally

Go to the folder of the SDK and build the container image using Docker.

```sh
docker build -t __SDK_IMAGE_NAME__ .
```

Go to tests folder and install the dependencies

```sh
npm ci
```

Set the required environment variables

```sh
export SAUCE_USERNAME=__YOUR_SAUCE_USERNAME__
export SAUCE_ACCESS_KEY=__YOUR_SAUCE_ACCESS_KEY__
export CONTAINER_IMAGE_NAME=__SDK_IMAGE_NAME__
```

Run the tests

```sh
npm run test
```
201 changes: 198 additions & 3 deletions tests/env-vars.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,198 @@
test('adds 1 + 2 to equal 3', () => {
expect(1+2).toBe(3);
});
import { BuildStatus, SauceRegion, getApi } from '@saucelabs/visual';
import {
RE_VISUAL_BUILD_ID,
RE_VISUAL_BUILD_LINK,
SAUCE_VISUAL_BRANCH,
SAUCE_VISUAL_BUILD_NAME,
SAUCE_VISUAL_DEFAULT_BRANCH,
SAUCE_VISUAL_PROJECT,
waitStatusForBuild,
} from './utils/helpers';
import { execute } from './utils/process';
import { FileHandle } from 'fs/promises';
import { randomBytes } from 'crypto';

const region = 'us-west-1' as SauceRegion;

const visualApi = getApi({
region,
user: process.env.SAUCE_USERNAME!,
key: process.env.SAUCE_ACCESS_KEY!,
});

const customId = randomBytes(20).toString('hex');

let fileOutput: FileHandle | undefined;
let dockerOutput = '';
let buildId = '';
let externalBuildId = '';

describe('Env var tests', () => {
it(
'creates an external build',
async () => {
const result = await execute(
`npx @saucelabs/visual build create -n "${SAUCE_VISUAL_BUILD_NAME}"`,
{
displayOutputOnFailure: true,
pipeOutput: false,
fileOutput,
}
);
expect(result.statusCode).toEqual(0);
const cliOutput = result.stdout;
const buildIds = [...cliOutput.matchAll(RE_VISUAL_BUILD_ID)];
expect(buildIds.length).toBe(1);
externalBuildId = buildIds[0][1];
},
2 * 60 * 1000
);

it(
'runs the docker image with SAUCE_VISUAL_BUILD_ID in place',
async () => {
const result = await execute(
`docker run --rm -e SAUCE_USERNAME -e SAUCE_ACCESS_KEY \\
-e SAUCE_VISUAL_BUILD_ID \\
${process.env.CONTAINER_IMAGE_NAME}`,
{
displayOutputOnFailure: true,
pipeOutput: false,
fileOutput,
env: {
SAUCE_VISUAL_BUILD_ID: externalBuildId,
},
}
);

expect(result.statusCode).toEqual(0);
dockerOutput = result.stdout;
},
2 * 60 * 1000
);

it(
'screenshots are linked to the external build',
async () => {
const build = await visualApi.buildWithDiffs(externalBuildId);
expect(build).toBeTruthy();
expect(build?.id).toEqual(externalBuildId);
expect(build?.name).toEqual(SAUCE_VISUAL_BUILD_NAME);
expect(build?.diffs?.nodes.length).toBe(1);
},
15 * 1000
);

it(
'creates an external build with customId',
async () => {
const result = await execute(
`npx @saucelabs/visual build create -n "${SAUCE_VISUAL_BUILD_NAME}" -c ${customId}`,
{
displayOutputOnFailure: true,
pipeOutput: false,
fileOutput,
}
);
expect(result.statusCode).toEqual(0);
const cliOutput = result.stdout;
const buildIds = [...cliOutput.matchAll(RE_VISUAL_BUILD_ID)];
expect(buildIds.length).toBe(1);
externalBuildId = buildIds[0][1];
},
2 * 60 * 1000
);

it(
'runs the docker image with SAUCE_VISUAL_CUSTOM_ID in place',
async () => {
const result = await execute(
`docker run --rm -e SAUCE_USERNAME -e SAUCE_ACCESS_KEY \\
-e SAUCE_VISUAL_CUSTOM_ID \\
${process.env.CONTAINER_IMAGE_NAME}`,
{
displayOutputOnFailure: true,
pipeOutput: false,
fileOutput,
env: {
SAUCE_VISUAL_CUSTOM_ID: customId,
},
}
);

expect(result.statusCode).toEqual(0);
dockerOutput = result.stdout;
},
2 * 60 * 1000
);

it(
'screenshots are linked to the external build with customId',
async () => {
const build = await visualApi.buildWithDiffsByCustomId(customId);
expect(build).toBeTruthy();
expect(build?.id).toEqual(externalBuildId);
expect(build?.name).toEqual(SAUCE_VISUAL_BUILD_NAME);
expect(build?.diffs?.nodes.length).toBe(1);
},
15 * 1000
);

it(
'runs the docker image with env vars in place',
async () => {
const result = await execute(
`docker run --rm -e SAUCE_USERNAME -e SAUCE_ACCESS_KEY \\
-e SAUCE_VISUAL_PROJECT \\
-e SAUCE_VISUAL_BRANCH \\
-e SAUCE_VISUAL_DEFAULT_BRANCH \\
-e SAUCE_VISUAL_BUILD_NAME \\
${process.env.CONTAINER_IMAGE_NAME}`,
{
displayOutputOnFailure: true,
pipeOutput: false,
fileOutput,
env: {
SAUCE_VISUAL_PROJECT: SAUCE_VISUAL_PROJECT,
SAUCE_VISUAL_BRANCH: SAUCE_VISUAL_BRANCH,
SAUCE_VISUAL_DEFAULT_BRANCH: SAUCE_VISUAL_DEFAULT_BRANCH,
SAUCE_VISUAL_BUILD_NAME: SAUCE_VISUAL_BUILD_NAME,
},
}
);

expect(result.statusCode).toEqual(0);
dockerOutput = result.stdout;
},
2 * 60 * 1000
);

it('returns a valid build link', async () => {
expect(dockerOutput.length).toBeGreaterThan(0);

const links = [...dockerOutput.matchAll(RE_VISUAL_BUILD_LINK)];
expect(links.length).toBe(1);
buildId = links[0][4];
});

it(
'env vars are processed correctly',
async () => {
expect(buildId).toMatch(RE_VISUAL_BUILD_ID);

await waitStatusForBuild(visualApi, buildId, [BuildStatus.Unapproved], {
refreshRate: 1000,
retries: 10,
});

const build = await visualApi.build(buildId);
expect(build).toBeTruthy();
expect(build?.id).toEqual(buildId);
expect(build?.name).toEqual(SAUCE_VISUAL_BUILD_NAME);
expect(build?.project).toBe(SAUCE_VISUAL_PROJECT);
expect(build?.branch).toBe(SAUCE_VISUAL_BRANCH);
expect(build?.defaultBranch).toBe(SAUCE_VISUAL_DEFAULT_BRANCH);
},
15 * 1000
);
});
10 changes: 10 additions & 0 deletions tests/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/tests/*.spec.ts'],
testPathIgnorePatterns: ['/project/', '/node_modules/'],
runner: './utils/runner.ts',
setupFilesAfterEnv: ['jest-extended/all'],
bail: 1,
};
2 changes: 1 addition & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
"dependencies": {
"@saucelabs/visual": "^0.3.352"
}
}
}
49 changes: 49 additions & 0 deletions tests/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Build, BuildStatus, VisualApi } from '@saucelabs/visual';

export const RE_VISUAL_BUILD_LINK =
/https:\/\/app\.(([a-z0-9-]+)\.)?saucelabs\.(com|net)\/visual\/builds\/([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})/g;
export const RE_VISUAL_BUILD_ID =
/([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})/g;

export const SAUCE_VISUAL_PROJECT = 'E2E test project';
export const SAUCE_VISUAL_BRANCH = 'E2E test branch';
export const SAUCE_VISUAL_DEFAULT_BRANCH = 'E2E test default branch';
export const SAUCE_VISUAL_BUILD_NAME = 'E2E test build name';

export const waitStatusForBuild = async function (
api: VisualApi,
buildId: string,
status: BuildStatus[],
options?: {
refreshRate?: number;
retries?: number;
buildIdType?: 'customId' | 'buildId';
}
): Promise<Partial<Build> | undefined> {
const {
refreshRate = 500,
buildIdType = 'buildId',
retries = 10,
} = options ?? {};
let currentTry = 0;
do {
currentTry++;
const build =
buildIdType === 'buildId'
? await api.build(buildId)
: await api.buildByCustomId(buildId);
if (build?.status && status.includes(build?.status)) {
return build;
}
await wait(refreshRate);
} while (currentTry < retries);
throw new Error(
`Expected status ${status} never received for build ${buildId}`
);
};

async function wait(delayMs: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, delayMs);
});
}
Loading

0 comments on commit ba6f21d

Please sign in to comment.