Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tikazyq committed Oct 22, 2024
0 parents commit 91f2030
Show file tree
Hide file tree
Showing 27 changed files with 740 additions and 0 deletions.
41 changes: 41 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
root = true

[*]
tab_width = 2
indent_size = 2
# charset = utf-8
# end_of_line = lf
# indent_size = 4
# indent_style = space
# insert_final_newline = true
# max_line_length = 120
# tab_width = 4
# trim_trailing_whitespace = true

[*.ts]
indent_size = 2

[*.scss]
indent_size = 2

[{*.ats, *.ts}]
# indent_size = 2
# tab_width = 2

[{*.js, *.cjs}]
# indent_size = 2
# tab_width = 2

[{*.sht, *.html, *.shtm, *.shtml, *.htm, *.ng}]
# indent_size = 2
# tab_width = 2

[{.analysis_options, *.yml, *.yaml}]
# indent_size = 2

[{.babelrc, .prettierrc, .stylelintrc, .eslintrc, jest.config, *.json, *.jsb3, *.jsb2, *.bowerrc}]
# indent_size = 2

[vue.config.js]
indent_size = 2
tab_width = 2
36 changes: 36 additions & 0 deletions .github/workflows/test-crawlab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Test Crawlab
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
services:
crawlab:
image: crawlabteam/crawlab:develop
ports:
- 8080:8080
mongo:
image: mongo:latest
ports:
- 27017:27017
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
.idea/
.env
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "avoid",
"endOfLine": "lf"
}
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Crawlab E2E Tests

This repository contains end-to-end tests for Crawlab.

## Getting Started

```
e2e/
├───fixtures # Test data (JSON files or static data)
├───page-objects # Page Object Model files for different pages
├───tests # Your actual test cases organized by feature or page
└───utils # Utility functions and reusable helpers
```
10 changes: 10 additions & 0 deletions fixtures/userData.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"adminUser": {
"username": "admin",
"password": "admin"
},
"invalidUser": {
"username": "invalid",
"password": "invalid"
}
}
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@crawlab/e2e-tests",
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "npx playwright test",
"test:headed": "npx playwright test --headed",
"test:debug": "npx playwright test --debug"
},
"devDependencies": {
"@playwright/test": "^1.48.1",
"@types/node": "^22.7.7",
"dotenv": "^16.4.5"
}
}
Empty file added page-objects/home/homePage.ts
Empty file.
47 changes: 47 additions & 0 deletions page-objects/layout/detailLayoutPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Page } from '@playwright/test';
import ListLayoutPage from '@/page-objects/layout/listLayoutPage';
import NormalLayoutPage from '@/page-objects/layout/normalLayoutPage';

export default abstract class DetailLayoutPage<T extends ListLayoutPage> extends NormalLayoutPage {
private listPage: T;

protected abstract getListPage(): T;

constructor(page: Page) {
super(page);
this.listPage = this.getListPage();
}

protected detailContainer = '.detail-layout';
protected backButton = '[data-test="back-btn"]';
protected saveButton = '[data-test="save-btn"]';
protected activeTabSelector = '.nav-tabs .el-menu-item.is-active';

async navigate(rowIndex?: number) {
await super.navigate();
await this.listPage.navigate();
await this.listPage.navigateToDetail(rowIndex || 0);
await this.waitForPageLoad();
}

async waitForPageLoad() {
await this.page.waitForSelector(this.detailContainer);
}

async getActiveTabName() {
const activeTab = this.page.locator(this.activeTabSelector);
return activeTab.textContent();
}

async switchToTab(tabName: string) {
await this.page.click(`.nav-tabs [data-test="${tabName}"]`);
}

async save() {
await this.page.click(this.saveButton);
}

async goBack() {
await this.page.click(this.backButton);
}
}
26 changes: 26 additions & 0 deletions page-objects/layout/listLayoutPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import NormalLayoutPage from '@/page-objects/layout/normalLayoutPage';

export default abstract class ListLayoutPage extends NormalLayoutPage {
protected abstract path: string;

protected listContainer = '.list-layout';
protected createButton = '#add-btn';
protected searchInput = '#filter-search .el-input input';
protected tableRows = '.list-layout table tbody tr';
protected viewButton = '.view-btn';

async navigate() {
await super.navigate();
await this.page.goto(this.path);
await this.waitForPageLoad();
}

async waitForPageLoad() {
await this.page.waitForSelector(this.listContainer);
}

async navigateToDetail(rowIndex: number) {
const row = this.page.locator(this.tableRows).nth(rowIndex);
await row.locator(this.viewButton).click();
}
}
60 changes: 60 additions & 0 deletions page-objects/layout/normalLayoutPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Page } from '@playwright/test';
import { LoginPage } from '@/page-objects/login/loginPage';
import userData from '@/fixtures/userData.json';

export default abstract class NormalLayoutPage {
protected page: Page;
protected loginPage: LoginPage;

constructor(page: Page) {
this.page = page;
this.loginPage = new LoginPage(page);
}

protected edition = '.sidebar .logo-sub-title .logo-sub-title-block:nth-child(1)';

async navigate() {
await this.page.goto('/'); // Navigate to the base URL
const isLoggedIn = await this.checkLoginStatus();
if (!isLoggedIn) {
await this.performLogin();
}
}

private async checkLoginStatus(): Promise<boolean> {
return await this.page.evaluate(() => {
const token = localStorage.getItem('jwt_token');
return !!token;
});
}

private async performLogin() {
await this.loginPage.navigate();
await this.loginPage.login(userData.adminUser.username, userData.adminUser.password);
// After successful login, store the JWT token
await this.storeJwtToken();
}

private async storeJwtToken() {
// Assuming the JWT token is stored in localStorage after login
// You may need to adjust this based on your actual implementation
await this.page.evaluate(() => {
const token = localStorage.getItem('your_jwt_token_key');
if (token) {
localStorage.setItem('jwt_token', token);
}
});
}

abstract waitForPageLoad(): Promise<void>;

async getEdition() {
return (await this.page.locator(this.edition).textContent())?.trim();
}

async isPro(): Promise<boolean> {
const edition = await this.getEdition();
if (!edition) return false;
return ['Pro', '专业版'].some(value => value.includes(edition));
}
}
40 changes: 40 additions & 0 deletions page-objects/login/loginPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Page } from '@playwright/test';

export class LoginPage {
private page: Page;
private usernameField = 'input[name="username"]';
private passwordField = 'input[name="password"]';
private submitButton = 'button[name="submit"]';
public errorMessage = '.el-message--error';

constructor(page: Page) {
this.page = page;
}

async navigate() {
await this.page.goto('/#/login');
await this.waitForPageLoad();
}

async waitForPageLoad() {
await this.page.waitForSelector(this.usernameField);
}

async login(username: string, password: string) {
await this.enterUsername(username);
await this.enterPassword(password);
await this.submit();
}

async enterUsername(username: string) {
await this.page.fill(this.usernameField, username);
}

async enterPassword(password: string) {
await this.page.fill(this.passwordField, password);
}

async submit() {
await this.page.click(this.submitButton);
}
}
47 changes: 47 additions & 0 deletions page-objects/node/nodeDetailPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { NodeListPage } from '@/page-objects/node/nodeListPage';
import DetailLayoutPage from '@/page-objects/layout/detailLayoutPage';

export class NodeDetailPage extends DetailLayoutPage<NodeListPage> {
private nameField = '.form-item[data-test="name"] input';
private typeSelector = '.form-item[data-test="type"] .el-tag';
private enabledSwitch = '.form-item[data-test="enabled"] .el-switch__input';
private maxRunnersField = '.form-item[data-test="max_runners"] input';
private descriptionField = '.form-item[data-test="description"] textarea';
private currentMetricsSelector = '.current-metrics';
private monitoringTabSelector = '.nav-tabs [data-test="monitoring"]';

getListPage(): NodeListPage {
return new NodeListPage(this.page);
}

async isMonitoringTabDisabled() {
const monitoringTab = this.page.locator(this.monitoringTabSelector);
if (!monitoringTab) return null;
return await monitoringTab.getAttribute('class').then(cls => cls?.includes('is-disabled'));
}

async getNodeName() {
return this.page.inputValue(this.nameField);
}

async getNodeType() {
return this.page.locator(this.typeSelector)?.textContent();
}

async isNodeEnabled() {
const ariaChecked = await this.page.locator(this.enabledSwitch)?.getAttribute('aria-checked');
return ariaChecked === 'true';
}

async getNodeMaxRunners() {
return this.page.inputValue(this.maxRunnersField);
}

async getNodeDescription() {
return this.page.inputValue(this.descriptionField);
}

async getCurrentMetrics() {
return this.page.locator(this.currentMetricsSelector)?.textContent();
}
}
Loading

0 comments on commit 91f2030

Please sign in to comment.