Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { BasePage } from './base';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
export class AuthenticationPage extends BasePage {
|
||||
url = `/sign-in`;
|
||||
signUpUrl = `/sign-up`;
|
||||
|
||||
async signIn(params: { email: string; password: string }) {
|
||||
await this.page.goto(this.url);
|
||||
|
||||
const emailField = this.page.getByTestId('sign-in-email');
|
||||
await emailField.click();
|
||||
await emailField.fill(params.email);
|
||||
|
||||
const passwordField = this.page.getByTestId('sign-in-password');
|
||||
await passwordField.click();
|
||||
await passwordField.fill(params.password);
|
||||
|
||||
await this.page.getByTestId('sign-in-button').click();
|
||||
}
|
||||
|
||||
async signUp(params?: { email?: string; password?: string; firstName?: string; lastName?: string }) {
|
||||
await this.page.goto(this.signUpUrl);
|
||||
|
||||
const firstNameField = this.page.getByTestId('sign-up-first-name');
|
||||
await firstNameField.click();
|
||||
await firstNameField.fill(params?.firstName || 'Bugs');
|
||||
await firstNameField.press('Tab');
|
||||
|
||||
const lastNameField = this.page.getByTestId('sign-up-last-name');
|
||||
await lastNameField.click();
|
||||
await lastNameField.fill(params?.lastName || 'Bunny');
|
||||
await lastNameField.press('Tab');
|
||||
|
||||
const emailField = this.page.getByTestId('sign-up-email');
|
||||
await emailField.click();
|
||||
await emailField.fill(params?.email || faker.internet.email());
|
||||
await emailField.press('Tab');
|
||||
|
||||
const passwordField = this.page.getByTestId('sign-up-password');
|
||||
await passwordField.click();
|
||||
await passwordField.fill(params?.password || faker.internet.password({
|
||||
pattern: /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?a-zA-Z0-9]/,
|
||||
length: 12,
|
||||
prefix: '0'
|
||||
}));
|
||||
|
||||
await this.page.getByTestId('sign-up-button').click();
|
||||
}
|
||||
}
|
||||
29
activepieces-fork/packages/tests-e2e/pages/base.ts
Normal file
29
activepieces-fork/packages/tests-e2e/pages/base.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
export interface IPageElement {
|
||||
(...args: unknown[]): Locator;
|
||||
}
|
||||
|
||||
export interface IPageAction {
|
||||
(...args: unknown[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IPageGetter {
|
||||
(...args: unknown[]): Locator;
|
||||
}
|
||||
|
||||
export interface IPageObject {
|
||||
url: string;
|
||||
page: Page;
|
||||
visit(): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class BasePage implements IPageObject {
|
||||
abstract url: string;
|
||||
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
async visit(): Promise<void> {
|
||||
await this.page.goto(this.url);
|
||||
}
|
||||
}
|
||||
60
activepieces-fork/packages/tests-e2e/pages/builder.page.ts
Normal file
60
activepieces-fork/packages/tests-e2e/pages/builder.page.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { BasePage } from './base';
|
||||
|
||||
export class BuilderPage extends BasePage {
|
||||
url = `/builder`;
|
||||
|
||||
async selectInitialTrigger(params: { piece: string; trigger: string }) {
|
||||
await this.page.getByTestId('rf__node-trigger').filter({ hasText: 'Select Trigger' }).click();
|
||||
await this.page.getByTestId('pieces-search-input').fill(params.trigger);
|
||||
await this.page.getByText(params.trigger).click();
|
||||
}
|
||||
|
||||
async addAction(params: { piece: string; action: string }) {
|
||||
await this.page.getByTestId('add-action-button').click();
|
||||
await this.page.getByTestId('pieces-search-input').fill(params.piece);
|
||||
await this.page.getByTestId(params.piece).click();
|
||||
await this.page.getByText(params.action).nth(1).click();
|
||||
}
|
||||
|
||||
async testFlowAndWaitForSuccess() {
|
||||
await this.page.getByRole('button', { name: 'Test Flow' }).click();
|
||||
await this.page.waitForTimeout(1000);
|
||||
const runSuccessLocator = this.page.locator('text=Run Succeeded');
|
||||
const runSuccessText = await runSuccessLocator.textContent({ timeout: 60000 });
|
||||
expect(runSuccessText).toContain('Run Succeeded');
|
||||
}
|
||||
|
||||
async testStep() {
|
||||
await this.page.getByRole('button', { name: 'Test Step Ctrl + G' }).click();
|
||||
await this.page.waitForTimeout(8000);
|
||||
}
|
||||
|
||||
async testTrigger() {
|
||||
await this.page.getByTestId('test-trigger-button').click();
|
||||
await this.page.waitForTimeout(5000);
|
||||
}
|
||||
|
||||
async handleDismissButton() {
|
||||
const dismissButton = this.page.getByRole('button', { name: 'Dismiss' });
|
||||
if (await dismissButton.isVisible()) {
|
||||
await dismissButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
async loadSampleData() {
|
||||
await this.page.getByText('Load Sample data').click();
|
||||
await this.page.waitForTimeout(8000);
|
||||
}
|
||||
|
||||
async publishFlow() {
|
||||
await this.page.getByRole('button', { name: 'Publish' }).click();
|
||||
await this.page.waitForTimeout(15000);
|
||||
}
|
||||
|
||||
async waitFor() {
|
||||
await this.page.waitForURL('**/flows/**');
|
||||
await this.page.waitForSelector('.react-flow__nodes', { state: 'visible' });
|
||||
await this.page.waitForSelector('.react-flow__node', { state: 'visible' });
|
||||
}
|
||||
}
|
||||
30
activepieces-fork/packages/tests-e2e/pages/flows.page.ts
Normal file
30
activepieces-fork/packages/tests-e2e/pages/flows.page.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { BasePage } from './base';
|
||||
export class FlowsPage extends BasePage {
|
||||
url = `/flows`;
|
||||
|
||||
async navigate() {
|
||||
await this.page.getByRole('link', { name: 'Flows' }).click();
|
||||
await this.page.waitForSelector('tbody tr');
|
||||
}
|
||||
|
||||
async waitFor() {
|
||||
await this.page.waitForSelector('tbody tr');
|
||||
}
|
||||
|
||||
async newFlowFromScratch() {
|
||||
await this.page.getByTestId('new-flow-button').click();
|
||||
await this.page.getByTestId('new-flow-from-scratch-button').click();
|
||||
}
|
||||
|
||||
async cleanupExistingFlows() {
|
||||
while ((await this.page.locator('span.text-muted-foreground').count()) > 1) {
|
||||
if (!(await this.page.locator('td:nth-child(7)').first().count())) break;
|
||||
await this.page.locator('td:nth-child(7)').first().click();
|
||||
await this.page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
const confirmButton = this.page.getByRole('button', { name: 'Remove' });
|
||||
await confirmButton.click();
|
||||
await this.page.waitForSelector('button:has-text("Remove")', { state: 'hidden' });
|
||||
await this.page.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
4
activepieces-fork/packages/tests-e2e/pages/index.ts
Normal file
4
activepieces-fork/packages/tests-e2e/pages/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { BasePage } from './base';
|
||||
export { AuthenticationPage } from './authentication.page';
|
||||
export { FlowsPage } from './flows.page';
|
||||
export { BuilderPage } from './builder.page';
|
||||
Reference in New Issue
Block a user