Skip to content

Commit

Permalink
Bash tool (#233)
Browse files Browse the repository at this point in the history
**Issue #226**

This PR introduces a Bash tool that enables using Shortest for API
testing

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sahil Lavingia <sahil.lavingia@gmail.com>
  • Loading branch information
3 people authored Jan 2, 2025
1 parent cfcd7a4 commit 8ad7a78
Show file tree
Hide file tree
Showing 15 changed files with 551 additions and 144 deletions.
157 changes: 100 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Your browser does not support the video tag.
</video>

## Features

- Natural language E2E testing framework
- AI-powered test execution using Anthropic Claude API
- Built on Playwright
Expand All @@ -22,13 +23,15 @@ Your browser does not support the video tag.
If helpful, [here's a short video](https://github.com/anti-work/shortest/issues/143#issuecomment-2564488173)!

### Installation

```bash
npm install -D @antiwork/shortest
# or
pnpm add -D @antiwork/shortest
```

Add `.shortest/` to your `.gitignore` (where Shortest stores screenshots of each test run):

```bash
echo ".shortest/" >> .gitignore
```
Expand All @@ -44,46 +47,51 @@ pnpm shortest
### Quick start

1. Determine your test entry and add your Anthropic API key in config file: `shortest.config.ts`
```typescript
import type { ShortestConfig } from '@antiwork/shortest';

export default {
headless: false,
baseUrl: 'http://localhost:3000',
testPattern: '**/*.test.ts',
anthropicKey: process.env.ANTHROPIC_API_KEY
} satisfies ShortestConfig;
```
```typescript
import type { ShortestConfig } from "@antiwork/shortest";

export default {
headless: false,
baseUrl: "http://localhost:3000",
testPattern: "**/*.test.ts",
anthropicKey: process.env.ANTHROPIC_API_KEY,
} satisfies ShortestConfig;
```

2. Write your test in your test directory: `app/__tests__/login.test.ts`
```typescript
import { shortest } from '@antiwork/shortest'

shortest('Login to the app using email and password', { username: process.env.GITHUB_USERNAME, password: process.env.GITHUB_PASSWORD })
```
```typescript
import { shortest } from "@antiwork/shortest";

shortest("Login to the app using email and password", {
username: process.env.GITHUB_USERNAME,
password: process.env.GITHUB_PASSWORD,
});
```

### Using callback functions

You can also use callback functions to add additional assertions and other logic. AI will execute the callback function after the test
execution in browser is completed.

```typescript
import { shortest } from '@antiwork/shortest';
import { db } from '@/lib/db/drizzle';
import { users } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';
import { shortest } from "@antiwork/shortest";
import { db } from "@/lib/db/drizzle";
import { users } from "@/lib/db/schema";
import { eq } from "drizzle-orm";

shortest('Login to the app using username and password', {
shortest("Login to the app using username and password", {
username: process.env.USERNAME,
password: process.env.PASSWORD
password: process.env.PASSWORD,
}).after(async ({ page }) => {
// Get current user's clerk ID from the page
const clerkId = await page.evaluate(() => {
return window.localStorage.getItem('clerk-user');
return window.localStorage.getItem("clerk-user");
});

if (!clerkId) {
throw new Error('User not found in database');
throw new Error("User not found in database");
}

// Query the database
Expand All @@ -102,11 +110,12 @@ shortest('Login to the app using username and password', {
You can use lifecycle hooks to run code before and after the test.

```typescript
import { shortest } from '@antiwork/shortest';
import { shortest } from "@antiwork/shortest";

shortest.beforeAll(async ({ page }) => {
await clerkSetup({
frontendApiUrl: process.env.PLAYWRIGHT_TEST_BASE_URL ?? "http://localhost:3000",
frontendApiUrl:
process.env.PLAYWRIGHT_TEST_BASE_URL ?? "http://localhost:3000",
});
});

Expand All @@ -115,7 +124,7 @@ shortest.beforeEach(async ({ page }) => {
page,
signInParams: {
strategy: "email_code",
identifier: "iffy+clerk_test@example.com"
identifier: "iffy+clerk_test@example.com",
},
});
});
Expand All @@ -137,20 +146,47 @@ Shortest supports flexible test chaining patterns:
// Sequential test chain
shortest([
"user can login with email and password",
"user can modify their account-level refund policy"
])
"user can modify their account-level refund policy",
]);

// Reusable test flows
const loginAsLawyer = "login as lawyer with valid credentials"
const loginAsContractor = "login as contractor with valid credentials"
const allAppActions = [
"send invoice to company",
"view invoices"
]
const loginAsLawyer = "login as lawyer with valid credentials";
const loginAsContractor = "login as contractor with valid credentials";
const allAppActions = ["send invoice to company", "view invoices"];

// Combine flows with spread operator
shortest([loginAsLawyer, ...allAppActions])
shortest([loginAsContractor, ...allAppActions])
shortest([loginAsLawyer, ...allAppActions]);
shortest([loginAsContractor, ...allAppActions]);
```

### API Testing

Test API endpoints using natural language

```typescript
const req = new APIRequest({
baseURL: API_BASE_URI,
});

shortest(
"Ensure the response contains only active users",
req.fetch({
url: "/users",
method: "GET",
params: new URLSearchParams({
active: true,
}),
}),
);
```

Or simply:

```typescript
shortest(`
Test the API GET endpoint ${API_BASE_URI}/users with query parameter { "active": true }
Expect the response to contain only active users
`);
```

### Running tests
Expand Down Expand Up @@ -189,13 +225,15 @@ GITHUB_TOTP_SECRET=your_secret # Only for GitHub auth tests
```

### CI setup

You can run Shortest in your CI/CD pipeline by running tests in headless mode. Make sure to add your Anthropic API key to your CI/CD pipeline secrets.

## Web app development

This guide will help you set up the Shortest web app for local development.

### Prerequisites

- React >=19.0.0 (if using with Next.js 14+ or Server Actions)
- Next.js >=14.0.0 (if using Server Components/Actions)

Expand All @@ -207,6 +245,7 @@ This guide will help you set up the Shortest web app for local development.
### Getting started

1. Clone the repository:

```bash
git clone https://github.com/anti-work/shortest.git
cd shortest
Expand All @@ -231,6 +270,7 @@ vercel env pull
```

#### For other contributors

1. Run `pnpm run setup` to configure the environment variables.
2. The setup wizard will ask you for information. Refer to "Services Configuration" section below for more details.

Expand All @@ -245,6 +285,7 @@ pnpm db:seed # creates stripe products, currently unused
### Services configuration

You'll need to set up the following services for local development. If you're not a Anti-Work Vercel team member, you'll need to either run the setup wizard `pnpm run setup` or manually configure each of these services and add the corresponding environment variables to your `.env.local` file:

<details>
<summary>Clerk</summary>

Expand Down Expand Up @@ -275,7 +316,7 @@ You'll need to set up the following services for local development. If you're no

1. Go to your dashboard at [anthropic.com](https://anthropic.com) and grab your API Key.
- Note: If you've never done this before, you will need to answer some questions and likely load your account with a balance. Not much is needed to test the app.
![Anthropic API Key](https://github.com/user-attachments/assets/0905ed4b-5815-4d50-bf43-8713a4397674)
![Anthropic API Key](https://github.com/user-attachments/assets/0905ed4b-5815-4d50-bf43-8713a4397674)

</details>

Expand All @@ -295,13 +336,14 @@ You'll need to set up the following services for local development. If you're no
<summary>GitHub OAuth</summary>

1. Create a GitHub OAuth App:

- Go to your GitHub account settings.
- Navigate to `Developer settings` > `OAuth Apps` > `New OAuth App`.
- Fill in the application details:
- **Application name**: Choose any name for your app
- **Homepage URL**: Set to `http://localhost:3000` for local development
- **Authorization callback URL**: Use the Clerk-provided callback URL (found in below image)
![Github OAuth App](https://github.com/user-attachments/assets/1af635fd-dedc-401c-a45a-159cb20bb209)
![Github OAuth App](https://github.com/user-attachments/assets/1af635fd-dedc-401c-a45a-159cb20bb209)

2. Configure Clerk with GitHub OAuth:
- Go to your Clerk dashboard.
Expand All @@ -310,18 +352,16 @@ You'll need to set up the following services for local development. If you're no
- Enter your `Client ID` and `Client Secret` from the GitHub OAuth app you just created.
- Add `repo` to the `Scopes`
![Clerk Custom Credentials](https://github.com/user-attachments/assets/31d414e1-4e1e-4725-8649-ec1826c6e53e)
</details>
</details>

<details>
<summary>Mailosaur</summary>

1. Go to [mailosaur.com](https://mailosaur.com) and create an account.
2. Create a new server and copy the Server ID.
3. Go to your API settings and copy your API key.
- You'll need both the Server ID and API key for your environment variables:
- `MAILOSAUR_API_KEY`: Your API key
- `MAILOSAUR_SERVER_ID`: Your server ID
</details>
- You'll need both the Server ID and API key for your environment variables: - `MAILOSAUR_API_KEY`: Your API key - `MAILOSAUR_SERVER_ID`: Your server ID
</details>

### Running locally

Expand All @@ -338,23 +378,26 @@ Open [http://localhost:3000](http://localhost:3000) in your browser to see the a
1. Make changes to the package source code in `packages/shortest/`

2. Test changes instantly during development (no build needed):
```bash
pnpm shortest:dev -h
```

```bash
pnpm shortest:dev -h
```

3. To test the actual built package:
```bash
pnpm build:pkg
pnpm shortest --help
```

```bash
pnpm build:pkg
pnpm shortest --help
```

4. To test in another project:
```bash
# In Shortest package directory
cd packages/shortest
pnpm pack

# In your test project
npm install /path/to/antiwork-shortest-{version}.tgz.tgz
npx shortest -h
```

```bash
# In Shortest package directory
cd packages/shortest
pnpm pack

# In your test project
npm install /path/to/antiwork-shortest-{version}.tgz.tgz
npx shortest -h
```
28 changes: 28 additions & 0 deletions app/api/test/assert-bearer/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { NextResponse } from "next/server";
import { ALLOWED_TEST_BEARER } from "@/lib/constants";
import { getBearerToken } from "@/lib/utils-server";

/**
* Asserts that the bearer token is present in the request
* If yes, returns the request body
*/
export async function POST(req: Request) {
const token = getBearerToken(req);

if (!token || token !== ALLOWED_TEST_BEARER) {
return NextResponse.json(
{ message: "Bearer token is missing in cookies" },
{ status: 401 },
);
}

try {
const body = await req.json();
return NextResponse.json(body);
} catch {
return NextResponse.json(
{ message: "Invalid request body" },
{ status: 400 },
);
}
}
27 changes: 27 additions & 0 deletions examples/api-assert-bearer-2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { shortest, APIRequest } from "@antiwork/shortest";
import { ALLOWED_TEST_BEARER, TESTING_API_BASE_URI } from "@/lib/constants";

const req = new APIRequest({
baseURL: TESTING_API_BASE_URI,
extraHTTPHeaders: {
"Content-Type": "application/json",
},
});

shortest(
"Ensure the request without a bearer token returns a message indicating the absence of the token",
req.fetch({
url: "/assert-bearer",
method: "POST",
body: JSON.stringify({ flagged: "false" }),
}),
);

shortest(
`Bearer token is ${ALLOWED_TEST_BEARER}. Ensure the request with a valid bearer token returns request body`,
req.fetch({
url: "/assert-bearer",
method: "POST",
body: JSON.stringify({ flagged: "true" }),
}),
);
13 changes: 13 additions & 0 deletions examples/api-assert-bearer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { shortest } from "@antiwork/shortest";
import { ALLOWED_TEST_BEARER, TESTING_API_BASE_URI } from "@/lib/constants";

// @note you should be authenticated in Clerk to run this test
shortest(`
Test the API POST endpoint ${TESTING_API_BASE_URI}/assert-bearer with body { "flagged": "false" } without providing a bearer token.
Expect the response to indicate that the token is missing
`);

shortest(`
Test the API POST endpoint ${TESTING_API_BASE_URI}/assert-bearer with body { "flagged": "true" } and the bearer token ${ALLOWED_TEST_BEARER}.
Expect the response to show "flagged": true
`);
Loading

0 comments on commit 8ad7a78

Please sign in to comment.