Skip to content

Commit ae191d8

Browse files
authored
chore(dashboard): user journey smoke tests (#7124)
1 parent 19f52e8 commit ae191d8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1563
-624
lines changed

.github/workflows/on-pr.yml

+8-8
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,14 @@ jobs:
256256
# with:
257257
# ee: true
258258

259-
# test_e2e_dashboard:
260-
# name: E2E test Dashboard app
261-
# needs: [get-affected]
262-
# if: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/dashboard') }}
263-
# uses: ./.github/workflows/reusable-dashboard-e2e.yml
264-
# secrets: inherit
265-
# with:
266-
# ee: true
259+
test_e2e_dashboard:
260+
name: E2E test Dashboard app
261+
needs: [get-affected]
262+
if: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/dashboard') }}
263+
uses: ./.github/workflows/reusable-dashboard-e2e.yml
264+
secrets: inherit
265+
with:
266+
ee: true
267267

268268
test_e2e_widget:
269269
name: E2E test Widget

.github/workflows/reusable-api-e2e.yml

-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ jobs:
109109
LAUNCH_DARKLY_SDK_KEY: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }}
110110
CI_EE_TEST: true
111111
CLERK_ENABLED: true
112-
CLERK_ISSUER_URL: ${{ vars.CLERK_ISSUER_URL }}
113112
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
114113
CLERK_WEBHOOK_SECRET: ${{ secrets.CLERK_WEBHOOK_SECRET }}
115114
CLERK_LONG_LIVED_TOKEN: ${{ secrets.CLERK_LONG_LIVED_TOKEN }}

.github/workflows/reusable-dashboard-e2e.yml

+47-18
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ jobs:
6868
with:
6969
submodules: true
7070

71+
- name: Create .env file for the Dashboard app
72+
working-directory: apps/dashboard
73+
run: |
74+
touch .env
75+
echo VITE_LAUNCH_DARKLY_CLIENT_SIDE_ID=${{ secrets.LAUNCH_DARKLY_CLIENT_SIDE_ID }} >> .env
76+
echo VITE_API_HOSTNAME=http://127.0.0.1:1336 >> .env
77+
echo VITE_WEBSOCKET_HOSTNAME=http://127.0.0.1:1340 >> .env
78+
echo VITE_LEGACY_DASHBOARD_URL=http://127.0.0.1:4200 >> .env
79+
echo VITE_CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_E2E_PUBLISHABLE_KEY }} >> .env
80+
81+
- name: Create .env file for the Playwright
82+
working-directory: apps/dashboard
83+
run: |
84+
touch .env.playwright
85+
echo NOVU_ENTERPRISE=true >> .env.playwright
86+
echo NEW_RELIC_ENABLED=false >> .env.playwright
87+
echo NEW_RELIC_APP_NAME=Novu >> .env.playwright
88+
echo MONGO_URL=mongodb://127.0.0.1:27017/novu-test >> .env.playwright
89+
echo API_URL=http://127.0.0.1:1336 >> .env.playwright
90+
echo CLERK_ENABLED=true >> .env.playwright
91+
echo CLERK_PUBLISHABLE_KEY=${{ secrets.CLERK_E2E_PUBLISHABLE_KEY }} >> .env.playwright
92+
echo CLERK_SECRET_KEY=${{ secrets.CLERK_E2E_SECRET_KEY }} >> .env.playwright
93+
echo NODE_ENV=test >> .env.playwright
94+
7195
- uses: mansagroup/nrwl-nx-action@v3
7296
with:
7397
targets: build
@@ -77,30 +101,35 @@ jobs:
77101
- uses: ./.github/actions/start-localstack
78102
- uses: ./.github/actions/setup-redis-cluster
79103

80-
- uses: ./.github/actions/run-backend
81-
with:
82-
cypress_github_oauth_client_id: ${{ secrets.CYPRESS_GITHUB_OAUTH_CLIENT_ID }}
83-
cypress_github_oauth_client_secret: ${{ secrets.CYPRESS_GITHUB_OAUTH_CLIENT_SECRET }}
84-
launch_darkly_sdk_key: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }}
85-
ci_ee_test: ${{ steps.determine_run_type.outputs.enterprise_run }}
104+
- name: Start API in TEST
105+
env:
106+
LAUNCH_DARKLY_SDK_KEY: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }}
107+
CI_EE_TEST: true
108+
CLERK_ENABLED: true
109+
CLERK_ISSUER_URL: https://neat-mole-83.clerk.accounts.dev
110+
CLERK_SECRET_KEY: ${{ secrets.CLERK_E2E_SECRET_KEY }}
111+
run: |
112+
cd apps/api && pnpm start:test &
113+
114+
- name: Start Worker
115+
shell: bash
116+
env:
117+
NODE_ENV: 'test'
118+
PORT: '1342'
119+
LAUNCH_DARKLY_SDK_KEY: ${{ secrets.LAUNCH_DARKLY_SDK_KEY }}
120+
CI_EE_TEST: true
121+
run: cd apps/worker && pnpm start:prod &
122+
123+
- name: Wait on API and Worker
124+
shell: bash
125+
run: wait-on --timeout=180000 http://127.0.0.1:1336/v1/health-check http://127.0.0.1:1342/v1/health-check
86126

87127
- name: Start WS
88128
run: |
89129
cd apps/ws && pnpm start:test &
90130
91-
- name: Start Novu Dashboard
92-
working-directory: apps/dashboard
93-
env:
94-
REACT_APP_API_URL: http://127.0.0.1:1336
95-
REACT_APP_WS_URL: http://127.0.0.1:1340
96-
REACT_APP_WEBHOOK_URL: http://127.0.0.1:1341
97-
# Disable LaunchDarkly client-side SDK in the test environment to reduce E2E flakiness
98-
REACT_APP_LAUNCH_DARKLY_CLIENT_SIDE_ID: ''
99-
NOVU_ENTERPRISE: ${{ steps.determine_run_type.outputs.enterprise_run }}
100-
run: pnpm start:static:build &
101-
102131
- name: Wait on Services
103-
run: wait-on --timeout=180000 http://127.0.0.1:1340/v1/health-check http://127.0.0.1:4201/
132+
run: wait-on --timeout=180000 http://127.0.0.1:1340/v1/health-check
104133

105134
- name: Install Playwright
106135
working-directory: apps/dashboard

.source

apps/dashboard/.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ tsconfig.node.tsbuildinfo
2626
/test-results/
2727
/playwright-report/
2828
/blob-report/
29-
/playwright/.cache/
29+
/playwright/
30+
.env.test
31+
.env.playwright

apps/dashboard/package.json

+9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"start": "vite",
8+
"start:test": "vite --mode test",
89
"start:static:build": "http-server dist -p 4201 --proxy http://127.0.0.1:4201?",
910
"dev": "pnpm start",
1011
"build": "tsc -b && vite build",
@@ -105,9 +106,15 @@
105106
"zod": "^3.23.8"
106107
},
107108
"devDependencies": {
109+
"@clerk/backend": "^1.6.2",
110+
"@clerk/testing": "^1.3.27",
108111
"@clerk/types": "^4.30.0",
109112
"@eslint/js": "^9.9.0",
113+
"@faker-js/faker": "^9.5.0",
114+
"@novu/ee-auth": "workspace:*",
110115
"@hookform/devtools": "^4.3.0",
116+
"@novu/dal": "workspace:*",
117+
"@novu/testing": "workspace:*",
111118
"@playwright/test": "^1.46.1",
112119
"@sentry/vite-plugin": "^2.22.6",
113120
"@tiptap/core": "^2.10.3",
@@ -121,6 +128,8 @@
121128
"@types/react-window": "^1.8.8",
122129
"@vitejs/plugin-react": "^4.3.1",
123130
"autoprefixer": "^10.4.20",
131+
"cross-fetch": "^4.0.0",
132+
"dotenv": "^16.4.5",
124133
"eslint": "^9.9.0",
125134
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
126135
"eslint-plugin-react-refresh": "^0.4.9",

apps/dashboard/playwright.config.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@ import { defineConfig, devices } from '@playwright/test';
44
* Read environment variables from file.
55
* https://github.com/motdotla/dotenv
66
*/
7-
// import dotenv from 'dotenv';
8-
// import path from 'path';
9-
// dotenv.config({ path: path.resolve(__dirname, '.env') });
7+
import dotenv from 'dotenv';
8+
import path from 'path';
9+
import { fileURLToPath } from 'url';
10+
import { dirname } from 'path';
11+
12+
const fileName = fileURLToPath(import.meta.url);
13+
const dirName = dirname(fileName);
14+
dotenv.config({ path: path.resolve(dirName, '.env.playwright') });
15+
16+
const baseURL = `http://localhost:4201`;
1017

1118
/**
1219
* See https://playwright.dev/docs/test-configuration.
@@ -24,9 +31,15 @@ export default defineConfig({
2431
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
2532
reporter: process.env.CI ? 'blob' : 'html',
2633
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
34+
webServer: {
35+
command: 'pnpm start:test',
36+
url: baseURL,
37+
timeout: 120 * 1000,
38+
reuseExistingServer: !process.env.CI,
39+
},
2740
use: {
2841
/* Base URL to use in actions like `await page.goto('/')`. */
29-
baseURL: 'http://127.0.0.1:8080',
42+
baseURL: baseURL,
3043

3144
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
3245
trace: 'on-first-retry',
@@ -40,7 +53,15 @@ export default defineConfig({
4053
projects: [
4154
{
4255
name: 'chromium',
43-
use: { ...devices['Desktop Chrome'] },
56+
testMatch: /.*\.e2e\.ts/,
57+
use: {
58+
...devices['Desktop Chrome'],
59+
viewport: { width: 1512, height: 982 },
60+
video: {
61+
mode: 'on-first-retry',
62+
size: { width: 1512, height: 982 },
63+
},
64+
},
4465
},
4566
],
4667
});

apps/dashboard/src/components/activity/activity-panel.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function ActivityPanel({ children }: ActivityPanelProps) {
1111
animate={{ opacity: 1 }}
1212
transition={{ duration: 0.5, ease: 'easeOut' }}
1313
className="flex h-full flex-col"
14+
data-testid="activity-panel"
1415
>
1516
{children}
1617
</motion.div>

apps/dashboard/src/components/icons/flags/us.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export function USFlag(props: React.SVGProps<SVGSVGElement>) {
22
return (
33
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
4-
<g id="United States" clip-path="url(#clip0_377_4883)">
4+
<g id="United States" clipPath="url(#clip0_377_4883)">
55
<path
66
id="Vector"
77
d="M8 15.999C12.4183 15.999 16 12.4173 16 7.99902C16 3.58075 12.4183 -0.000976562 8 -0.000976562C3.58172 -0.000976562 0 3.58075 0 7.99902C0 12.4173 3.58172 15.999 8 15.999Z"

apps/dashboard/src/components/in-app-action-dropdown.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ export const InAppActionDropdown = ({ onMenuItemClick }: { onMenuItemClick?: ()
8080
<DropdownMenuTrigger className="absolute size-full" tabIndex={-1} />
8181
</div>
8282
<DropdownMenuTrigger asChild>
83-
<CompactButton icon={RiExpandUpDownLine} size="lg" variant="ghost">
83+
<CompactButton
84+
icon={RiExpandUpDownLine}
85+
size="lg"
86+
variant="ghost"
87+
data-testid="in-app-action-dropdown-trigger"
88+
>
8489
<span className="sr-only">Actions</span>
8590
</CompactButton>
8691
</DropdownMenuTrigger>

apps/dashboard/src/components/primitives/tag-input.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,20 @@ const TagInput = forwardRef<HTMLInputElement, TagInputProps>((props, ref) => {
8787
</PopoverAnchor>
8888
<div className="flex flex-wrap gap-2">
8989
{tags.map((tag, index) => (
90-
<Tag key={index} variant="stroke" onDismiss={() => removeTag(tag)}>
91-
<span style={{ wordBreak: 'break-all' }}>{tag}</span>
90+
<Tag
91+
key={index}
92+
variant="stroke"
93+
onDismiss={(e) => {
94+
e.preventDefault();
95+
e.stopPropagation();
96+
97+
removeTag(tag);
98+
}}
99+
dismissTestId={`tags-badge-remove-${tag}`}
100+
>
101+
<span style={{ wordBreak: 'break-all' }} data-testid="tags-badge-value">
102+
{tag}
103+
</span>
92104
</Tag>
93105
))}
94106
</div>

apps/dashboard/src/components/primitives/tag.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,19 @@ TagDismissIcon.displayName = TAG_DISMISS_ICON_NAME;
161161
type TagProps = {
162162
children: React.ReactNode;
163163
icon?: React.ReactElement;
164-
onDismiss?: () => void;
164+
onDismiss?: React.MouseEventHandler<HTMLButtonElement>;
165165
asChild?: boolean;
166166
className?: string;
167+
dismissTestId?: string;
167168
} & Pick<VariantProps<typeof tagVariants>, 'variant' | 'disabled'>;
168169

169170
const Tag = React.forwardRef<HTMLDivElement, TagProps>(
170-
({ children, icon, onDismiss, asChild, variant, disabled, className }, ref) => {
171+
({ children, icon, onDismiss, asChild, variant, disabled, className, dismissTestId, ...rest }, ref) => {
171172
return (
172-
<TagRoot ref={ref} asChild={asChild} variant={variant} disabled={disabled} className={className}>
173+
<TagRoot ref={ref} asChild={asChild} variant={variant} disabled={disabled} className={className} {...rest}>
173174
{icon && <TagIcon as={icon.type} {...icon.props} />}
174175
{children}
175-
{onDismiss && <TagDismissButton onClick={onDismiss} disabled={disabled} />}
176+
{onDismiss && <TagDismissButton onClick={onDismiss} disabled={disabled} data-testid={dismissTestId} />}
176177
</TagRoot>
177178
);
178179
}

apps/dashboard/src/components/workflow-editor/add-step-menu.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const MenuItem = ({
5050
'text-foreground-300 cursor-not-allowed': disabled,
5151
}
5252
)}
53+
data-testid={`add-step-menu-item-${stepType}`}
5354
>
5455
<Icon
5556
className={`bg-neutral-alpha-50 h-6 w-6 rounded-md p-1 opacity-40`}
@@ -89,7 +90,7 @@ export const AddStepMenu = ({
8990
}}
9091
>
9192
<PopoverTrigger asChild>
92-
<span data-test-id="add-step-button">
93+
<span data-testid="add-step-menu-button">
9394
<Node
9495
variant="sm"
9596
className={cn('opacity-0 transition duration-300 ease-out hover:opacity-100', {

apps/dashboard/src/components/workflow-editor/base-node.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export const NodeBody = ({
8080

8181
return (
8282
<HoverCard openDelay={300}>
83-
<HoverCardTrigger>
83+
<HoverCardTrigger asChild>
8484
<div className="bg-neutral-alpha-50 hover-trigger pointer-events-auto relative flex items-center rounded-lg px-1 py-2">
8585
<span className="text-foreground-400 overflow-hidden text-ellipsis text-nowrap text-sm font-medium">
8686
{children}

apps/dashboard/src/components/workflow-editor/in-app-preview.tsx

+12-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ export const InAppPreviewSubject = (props: InAppPreviewSubjectProps) => {
106106
return <Skeleton className="h-5 w-1/2" />;
107107
}
108108

109-
return <Markdown className={cn('text-foreground-600 truncate text-xs font-medium', className)} {...rest} />;
109+
return (
110+
<Markdown
111+
className={cn('text-foreground-600 truncate text-xs font-medium', className)}
112+
{...rest}
113+
data-testid="in-app-preview-subject"
114+
/>
115+
);
110116
};
111117

112118
type InAppPreviewBodyProps = MarkdownProps & { isPending?: boolean };
@@ -124,7 +130,11 @@ export const InAppPreviewBody = (props: InAppPreviewBodyProps) => {
124130
}
125131

126132
return (
127-
<Markdown className={cn('text-foreground-400 whitespace-pre-wrap text-xs font-normal', className)} {...rest} />
133+
<Markdown
134+
className={cn('text-foreground-400 whitespace-pre-wrap text-xs font-normal', className)}
135+
{...rest}
136+
data-testid="in-app-preview-body"
137+
/>
128138
);
129139
};
130140

0 commit comments

Comments
 (0)