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

test: write workflow snapshot tests #381

Merged
merged 22 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
58 changes: 57 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ executors:
- image: cimg/node:20.13.1
resource_class: medium
working_directory: ~/project
ci-macos:
macos:
xcode: 15.0.0 # Use the closest available version of macOS (Sonoma equivalent may not be available yet)
working_directory: ~/project

commands:
attach_project:
Expand Down Expand Up @@ -75,10 +79,31 @@ jobs:
paths:
- node_modules
- packages/self-service/node_modules
# - packages/uikit/node_modules --> submodules are not cached
bang9 marked this conversation as resolved.
Show resolved Hide resolved
- persist_to_workspace:
root: .
paths: .

# CI - install dependencies for macOS
prepare-macos:
executor: ci-macos
steps:
- checkout
- attach_workspace:
at: /Users/distiller/project
- run:
name: Install dependencies
command: |
git submodule update --init --recursive
yarn install --immutable
- save_cache:
key: v2-dependencies-{{ checksum "yarn.lock" }}
paths:
- node_modules
- packages/self-service/node_modules
- persist_to_workspace:
root: /Users/distiller/project
paths: .

# CI - check format
run-format:
executor: ci-node
Expand All @@ -105,6 +130,31 @@ jobs:
no_output_timeout: 15m
name: Run test
command: yarn test
# CI - Run snapshot tests
run-snapshot-test:
executor: ci-macos
steps:
- attach_project
- run:
name: Clean and reinstall dependencies
command: |
rm -rf node_modules package-lock.json
yarn install
bang9 marked this conversation as resolved.
Show resolved Hide resolved
- run:
name: Install Playwright dependencies
command: sudo yarn playwright install-deps
- run:
name: Install Playwright browsers
command: yarn playwright install
bang9 marked this conversation as resolved.
Show resolved Hide resolved
- run:
name: List all __visual_tests__ snapshots
command: find /Users/distiller/project/__visual_tests__ -type f
bang9 marked this conversation as resolved.
Show resolved Hide resolved
- run:
name: Run Playwright snapshot tests
command: yarn playwright test
- store_artifacts:
path: /Users/distiller/project/test-results
liamcho marked this conversation as resolved.
Show resolved Hide resolved
destination: playwright-test-results
bang9 marked this conversation as resolved.
Show resolved Hide resolved

# Publish - build self-service
build:
Expand Down Expand Up @@ -201,6 +251,12 @@ workflows:
- run-test:
requires:
- prepare
ci-snapshot-test:
jobs:
- prepare-macos
- run-snapshot-test:
requires:
- prepare-macos

deploy_prod:
when: << pipeline.parameters.run_deploy_prod >>
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
4 changes: 4 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
compressionLevel: mixed

enableGlobalCache: false

bang9 marked this conversation as resolved.
Show resolved Hide resolved
nodeLinker: node-modules
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions __visual_tests__/const.ts
bang9 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const appId = process.env.SNAPSHOT_TEST_APP_ID;
const botId = process.env.SNAPSHOT_TEST_BOT_ID;

export const TEST_URL = `http://localhost:5173/chat-ai-widget/?app_id=${appId}&bot_id=${botId}&disable_timestamps=true`;
// export const TEST_URL = `http://localhost:5173/chat-ai-widget/?app_id=833E2DC4-DFA2-4508-A283-6E5C7BFCF18A&bot_id=onboarding_bot&disable_timestamps=true`;

export const WidgetComponentIds = {
WIDGET: '#aichatbot-widget-window',
WIDGET_BUTTON: '#aichatbot-widget-button',
MESSAGE_INPUT: '#sendbird-message-input-text-field',
SUGGESTED_REPLIES_OPTIONS: '.sendbird-suggested-replies__option',
BUTTON: 'button.sendbird-button--primary',
INPUT: '.sendbird-input__input',
CHIPS_CONTAINER: '.sendbird-form-chip__container',
};
31 changes: 31 additions & 0 deletions __visual_tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {expect, Page} from "@playwright/test";

import {WidgetComponentIds} from "./const";

export async function assertScreenshot(page: Page, screenshotName: string, browserName: string) {
bang9 marked this conversation as resolved.
Show resolved Hide resolved
const name = `${screenshotName}.${browserName}.png`; // Include the browser name in the filename
await expect(page.locator(WidgetComponentIds.WIDGET)).toHaveScreenshot(
name,
{
omitBackground: false,
maxDiffPixelRatio: 0.01, // Uncomment if you need a pixel threshold
}
);
}

export async function loadWidget(page: Page) {
await page.click(WidgetComponentIds.WIDGET_BUTTON);
await page.waitForTimeout(2500);
}
bang9 marked this conversation as resolved.
Show resolved Hide resolved

export async function sendTextMessage(page: Page, text: string, waitTime = 1000) {
const input = page.locator(WidgetComponentIds.MESSAGE_INPUT);
await input.fill(text);
await input.press('Enter');
await page.waitForTimeout(waitTime);
}

export async function clickNthChip(page: Page, nth: number) {
const chipContainer = page.locator(WidgetComponentIds.CHIPS_CONTAINER);
await chipContainer.locator(':scope > *').nth(nth).click();
}
125 changes: 125 additions & 0 deletions __visual_tests__/workflow-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {test} from '@playwright/test';

import {TEST_URL, WidgetComponentIds} from "./const";
import {assertScreenshot, clickNthChip, loadWidget, sendTextMessage} from "./utils";


test.beforeEach(async ({ page }) => {
await page.goto(TEST_URL);
await page.waitForTimeout(2500);
bang9 marked this conversation as resolved.
Show resolved Hide resolved
});

/**
* 100
* Workflow - Form message
* Steps:
* 1. Send the trigger message: "Give me a food order form"
* 2. Submit form without filling the required fields.
* 3. Submit form with at least one invalid value.
* 4. Submit form with valid values.
*/
test('100', async ({ page, browserName }) => {
await loadWidget(page);

// 1
await sendTextMessage(page, 'Give me a food order form');
await assertScreenshot(page, '100-1', browserName);

// 2
let submitButton = page.locator(WidgetComponentIds.BUTTON);
await submitButton.click();
await assertScreenshot(page, '100-2', browserName);

// 3
const inputs = page.locator(WidgetComponentIds.INPUT);
await inputs.nth(0).fill('guy ordering food');
await inputs.nth(2).fill('not a number');
await inputs.nth(3).fill('not.a.valid.email.com');
await inputs.nth(4).fill('123_456_7890');
await clickNthChip(page, 4);
submitButton = page.locator(WidgetComponentIds.BUTTON);
await page.waitForTimeout(1000);
await assertScreenshot(page, '100-3', browserName);

// 4
await inputs.nth(2).fill('2');
await inputs.nth(3).fill('[email protected]');
await inputs.nth(4).fill('123-456-7890');
await submitButton.click();
await page.waitForTimeout(1000);
await assertScreenshot(page, '100-4', browserName);
});

/**
* 101
* Workflow - Function calls: user message
* Steps:
* 1. Send the trigger message: "Tell me about one cat breed"
*/
test('101', async ({ page, browserName }) => {
await loadWidget(page);
// 1
await sendTextMessage(page, 'Tell me about one cat breed', 2000);
await assertScreenshot(page, '101-1', browserName);
});

/**
* 102
* Workflow - File message
* Steps:
* 1. Send the trigger message: "Give me a travel agency poster"
*/
test('102', async ({ page, browserName }) => {
await loadWidget(page);
// 1
await sendTextMessage(page, 'Give me a travel agency poster', 5000);
await assertScreenshot(page, '102-1', browserName);
});

/**
* 103
* Workflow - Suggested replies with 'Back' enabled
* Steps:
* 1. Send the trigger message: "Suggested replies"
* 2. Click "Text"
* 3. Click "Back"
* 4. Click "File"
* 5. Click "Back"
* 6. Click "Link to workflow: form message"
*/
test('103', async ({ page, browserName }) => {
await loadWidget(page);
// 1
await sendTextMessage(page, 'Suggested replies', 2000);
await assertScreenshot(page, '103-1', browserName);

// 2
let options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).click();
await page.waitForTimeout(1000);
await assertScreenshot(page, '103-2', browserName);

// 3
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).click();
await page.waitForTimeout(1000);
await assertScreenshot(page, '103-3', browserName);

// 4
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(1).click();
await page.waitForTimeout(3000);
await assertScreenshot(page, '103-4', browserName);

// 5
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(0).click();
await page.waitForTimeout(1000);
await assertScreenshot(page, '103-5', browserName);

// 6
options = page.locator(WidgetComponentIds.SUGGESTED_REPLIES_OPTIONS);
await options.nth(2).click();
await page.waitForTimeout(1000);
await assertScreenshot(page, '103-6', browserName);
});
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "@sendbird/chat-ai-widget",
"version": "1.8.5",
"private": true,
bang9 marked this conversation as resolved.
Show resolved Hide resolved
bang9 marked this conversation as resolved.
Show resolved Hide resolved
"description": "Sendbird Chat AI Widget,\n Detailed documentation can be found at https://github.com/sendbird/chat-ai-widget#readme",
"main": "./dist/index.umd.js",
"module": "./dist/index.es.js",
Expand Down Expand Up @@ -37,7 +38,9 @@
"@linaria/atomic": "^6.2.0",
"@linaria/core": "^6.2.0",
"@linaria/react": "^6.2.1",
"@playwright/test": "^1.48.1",
"@types/dompurify": "^3.0.5",
"@types/node": "^22.7.9",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.11",
"@types/styled-components": "^5.1.26",
Expand Down
80 changes: 80 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {defineConfig, devices} from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './__visual_tests__',
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}', // Refer to: https://playwright.dev/docs/next/api/class-testproject#test-project-snapshot-path-template
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: 0, // process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: undefined, // process.env.CI ? 1 : undefined,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

undefined allows using maximum possible number of workers

/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }, // Note we cannot use ...devices['Desktop Chrome'] because the name varies between devices and in CircleCI environment. CI test will fail because of this.
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }, // Note we cannot use ...devices['Desktop Firefox'] because the name varies between devices and in CircleCI environment. CI test will fail because of this.
},

// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn dev',
url: 'http://localhost:5173/chat-ai-widget/',
reuseExistingServer: !process.env.CI,
},
});
Loading