Token导航 LogoToken导航

Agent Skill · 待分类

playwright-fixtures-and-hooks(Playwright fixtures AND hooks 测试)

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

playwright-fixtures-and-hooks 是待分类类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为操作浏览器;许可证为 MIT,第三方扫描已通过,按安装量在待分类类的 11,209 个 Skill 里排第 5,030。

总安装 630周安装 26GitHub Stars 142下载 206核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:playwright-fixtures-and-hooks(Playwright fixtures AND hooks 测试)
来源仓库:https://github.com/thebushidocollective/han
仓库路径:skills/playwright-fixtures-and-hooks
安装命令:
npx skills add https://github.com/thebushidocollective/han --skill playwright-fixtures-and-hooks
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/thebushidocollective/han --skill playwright-fixtures-and-hooks

这个 Skill 是做什么的

这个 Skill 的分类还没最终确定,用法以来源仓库的说明为准。

用于辅助测试设计、自动化测试、用例整理和回归验证。适合编写单元测试、端到端测试或根据失败日志定位问题。使用时需确认项目测试框架、运行命令和夹具数据,避免误改逻辑。涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

playwright-fixtures-and-hooks 属于待分类类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/playwright-fixtures-and-hooks

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/thebushidocollective/han --skill playwright-fi…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要操作浏览器,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

playwright-fixtures-and-hooks 怎么安装?

在终端执行 npx skills add https://github.com/thebushidocollective/han --skill playwright-fixtures-and…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

playwright-fixtures-and-hooks 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

playwright-fixtures-and-hooks 需要什么权限?

权限级别是操作浏览器。该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

playwright-fixtures-and-hooks 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

playwright-fixtures-and-hooks 用的人多吗?

公开数据是累计安装 630 次,近一周安装 26 次,下载 206 次,GitHub 142 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

playwright-fixtures-and-hooks 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理待分类类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

待分类类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
infsh-cli

infsh CLI · Codex、Claude

4,374,576382权限未标注未明确未收录
ai-video-generation

AI 视频生成 · Codex、Claude

4,313,376382调用外部服务未明确有提醒
lark-openapi-explorer

云雀 OpenAPI 浏览器 · Codex、Claude

2,064,1209,011需要联网未明确有提醒
lark-workflow-standup-report

Lark 工作流程站立报告 · Codex、Claude

2,041,6328,978需要联网未明确通过
use-my-browser

使用我的浏览器 · Codex、Claude

1,848,49140操作浏览器未明确未通过
playwright-fixtures-and-hooks

Playwright fixtures AND hooks 测试 · 本页

630142操作浏览器MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Playwright Fixtures and Hooks

Master Playwright's fixture system and lifecycle hooks to create reusable test infrastructure, manage test state, and build maintainable test suites. This skill covers built-in fixtures, custom fixtures, and best practices for test setup and teardown.

Built-in Fixtures

Core Fixtures

import { test, expect } from '@playwright/test';

test('using built-in fixtures', async ({
  page,      // Page instance
  context,   // Browser context
  browser,   // Browser instance
  request,   // API request context
}) => {
  // Each test gets fresh page and context
  await page.goto('https://example.com');
  await expect(page).toHaveTitle(/Example/);
});

Page Fixture

test('page fixture examples', async ({ page }) => {
  // Navigate
  await page.goto('https://example.com');

  // Interact
  await page.getByRole('button', { name: 'Click me' }).click();

  // Wait
  await page.waitForLoadState('networkidle');

  // Evaluate
  const title = await page.title();
  expect(title).toBe('Example Domain');
});

Context Fixture

test('context fixture examples', async ({ context, page }) => {
  // Add cookies
  await context.addCookies([
    {
      name: 'session',
      value: 'abc123',
      domain: 'example.com',
      path: '/',
    },
  ]);

  // Set permissions
  await context.grantPermissions(['geolocation']);

  // Create additional page in same context
  const page2 = await context.newPage();
  await page2.goto('https://example.com');

  // Both pages share cookies and storage
  await page.goto('https://example.com');
});

Browser Fixture

test('browser fixture examples', async ({ browser }) => {
  // Create custom context with options
  const context = await browser.newContext({
    viewport: { width: 1920, height: 1080 },
    locale: 'en-US',
    timezoneId: 'America/New_York',
    permissions: ['geolocation'],
  });

  const page = await context.newPage();
  await page.goto('https://example.com');

  await context.close();
});

Request Fixture

test('API testing with request fixture', async ({ request }) => {
  // Make GET request
  const response = await request.get('https://api.example.com/users');
  expect(response.ok()).toBeTruthy();
  expect(response.status()).toBe(200);

  const users = await response.json();
  expect(users).toHaveLength(10);

  // Make POST request
  const createResponse = await request.post('https://api.example.com/users', {
    data: {
      name: 'John Doe',
      email: 'john@example.com',
    },
  });
  expect(createResponse.ok()).toBeTruthy();
});

Custom Fixtures

Basic Custom Fixture

// fixtures/base-fixtures.ts
import { test as base } from '@playwright/test';

type MyFixtures = {
  timestamp: string;
};

export const test = base.extend<MyFixtures>({
  timestamp: async ({}, use) => {
    const timestamp = new Date().toISOString();
    await use(timestamp);
  },
});

export { expect } from '@playwright/test';
// tests/example.spec.ts
import { test, expect } from '../fixtures/base-fixtures';

test('using custom timestamp fixture', async ({ timestamp, page }) => {
  console.log(`Test started at: ${timestamp}`);
  await page.goto('https://example.com');
});

Fixture with Setup and Teardown

import { test as base } from '@playwright/test';

type DatabaseFixtures = {
  database: Database;
};

export const test = base.extend<DatabaseFixtures>({
  database: async ({}, use) => {
    // Setup: Create database connection
    const db = await createDatabaseConnection();
    console.log('Database connected');

    // Provide fixture to test
    await use(db);

    // Teardown: Close database connection
    await db.close();
    console.log('Database closed');
  },
});

Fixture Scopes: Test vs Worker

import { test as base } from '@playwright/test';

type TestScopedFixtures = {
  uniqueId: string;
};

type WorkerScopedFixtures = {
  apiToken: string;
};

export const test = base.extend<TestScopedFixtures, WorkerScopedFixtures>({
  // Test-scoped: Created for each test
  uniqueId: async ({}, use) => {
    const id = `test-${Date.now()}-${Math.random()}`;
    await use(id);
  },

  // Worker-scoped: Created once per worker
  apiToken: [
    async ({}, use) => {
      const token = await generateApiToken();
      await use(token);
      await revokeApiToken(token);
    },
    { scope: 'worker' },
  ],
});

Authentication Fixtures

Authenticated User Fixture

// fixtures/auth-fixtures.ts
import { test as base } from '@playwright/test';

type AuthFixtures = {
  authenticatedPage: Page;
};

export const test = base.extend<AuthFixtures>({
  authenticatedPage: async ({ browser }, use) => {
    // Create new context with authentication
    const context = await browser.newContext({
      storageState: 'auth.json',
    });

    const page = await context.newPage();
    await use(page);

    await context.close();
  },
});

export { expect } from '@playwright/test';

Multiple User Roles

// fixtures/multi-user-fixtures.ts
import { test as base } from '@playwright/test';

type UserFixtures = {
  adminPage: Page;
  userPage: Page;
  guestPage: Page;
};

export const test = base.extend<UserFixtures>({
  adminPage: async ({ browser }, use) => {
    const context = await browser.newContext({
      storageState: 'auth/admin.json',
    });
    const page = await context.newPage();
    await use(page);
    await context.close();
  },

  userPage: async ({ browser }, use) => {
    const context = await browser.newContext({
      storageState: 'auth/user.json',
    });
    const page = await context.newPage();
    await use(page);
    await context.close();
  },

  guestPage: async ({ browser }, use) => {
    const context = await browser.newContext();
    const page = await context.newPage();
    await use(page);
    await context.close();
  },
});

Authentication Setup

// auth/setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate as admin', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.getByLabel('Email').fill('admin@example.com');
  await page.getByLabel('Password').fill('admin123');
  await page.getByRole('button', { name: 'Login' }).click();

  await page.waitForURL('**/dashboard');

  await page.context().storageState({ path: 'auth/admin.json' });
});

setup('authenticate as user', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('user123');
  await page.getByRole('button', { name: 'Login' }).click();

  await page.waitForURL('**/dashboard');

  await page.context().storageState({ path: 'auth/user.json' });
});

Database Fixtures

Test Database Fixture

// fixtures/database-fixtures.ts
import { test as base } from '@playwright/test';
import { PrismaClient } from '@prisma/client';

type DatabaseFixtures = {
  db: PrismaClient;
  cleanDb: void;
};

export const test = base.extend<DatabaseFixtures>({
  db: [
    async ({}, use) => {
      const db = new PrismaClient();
      await use(db);
      await db.$disconnect();
    },
    { scope: 'worker' },
  ],

  cleanDb: async ({ db }, use) => {
    // Clean database before test
    await db.user.deleteMany();
    await db.product.deleteMany();
    await db.order.deleteMany();

    await use();

    // Clean database after test
    await db.user.deleteMany();
    await db.product.deleteMany();
    await db.order.deleteMany();
  },
});

Seeded Data Fixture

// fixtures/seed-fixtures.ts
import { test as base } from './database-fixtures';

type SeedFixtures = {
  testUser: User;
  testProducts: Product[];
};

export const test = base.extend<SeedFixtures>({
  testUser: async ({ db, cleanDb }, use) => {
    const user = await db.user.create({
      data: {
        email: 'test@example.com',
        name: 'Test User',
        password: 'hashedpassword',
      },
    });

    await use(user);
  },

  testProducts: async ({ db, cleanDb }, use) => {
    const products = await db.product.createMany({
      data: [
        { name: 'Product 1', price: 10.99 },
        { name: 'Product 2', price: 20.99 },
        { name: 'Product 3', price: 30.99 },
      ],
    });

    const allProducts = await db.product.findMany();
    await use(allProducts);
  },
});

API Mocking Fixtures

Mock API Fixture

// fixtures/mock-api-fixtures.ts
import { test as base } from '@playwright/test';

type MockApiFixtures = {
  mockApi: void;
};

export const test = base.extend<MockApiFixtures>({
  mockApi: async ({ page }, use) => {
    // Mock API responses
    await page.route('**/api/users', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify([
          { id: 1, name: 'User 1' },
          { id: 2, name: 'User 2' },
        ]),
      });
    });

    await page.route('**/api/products', async (route) => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify([
          { id: 1, name: 'Product 1', price: 10 },
          { id: 2, name: 'Product 2', price: 20 },
        ]),
      });
    });

    await use();

    // Cleanup: Unroute all
    await page.unrouteAll();
  },
});

Conditional Mocking

// fixtures/conditional-mock-fixtures.ts
import { test as base } from '@playwright/test';

type ConditionalMockFixtures = {
  mockFailedApi: void;
  mockSlowApi: void;
};

export const test = base.extend<ConditionalMockFixtures>({
  mockFailedApi: async ({ page }, use) => {
    await page.route('**/api/**', async (route) => {
      await route.fulfill({
        status: 500,
        contentType: 'application/json',
        body: JSON.stringify({ error: 'Internal Server Error' }),
      });
    });

    await use();
    await page.unrouteAll();
  },

  mockSlowApi: async ({ page }, use) => {
    await page.route('**/api/**', async (route) => {
      // Simulate slow network
      await new Promise((resolve) => setTimeout(resolve, 3000));
      await route.continue();
    });

    await use();
    await page.unrouteAll();
  },
});

Lifecycle Hooks

Test Hooks

import { test, expect } from '@playwright/test';

test.describe('User Management', () => {
  test.beforeAll(async () => {
    // Runs once before all tests in this describe block
    console.log('Setting up test suite');
  });

  test.beforeEach(async ({ page }) => {
    // Runs before each test
    await page.goto('https://example.com');
    console.log('Test starting');
  });

  test.afterEach(async ({ page }, testInfo) => {
    // Runs after each test
    console.log(`Test ${testInfo.status}: ${testInfo.title}`);

    if (testInfo.status !== testInfo.expectedStatus) {
      // Test failed - capture additional debug info
      const screenshot = await page.screenshot();
      await testInfo.attach('failure-screenshot', {
        body: screenshot,
        contentType: 'image/png',
      });
    }
  });

  test.afterAll(async () => {
    // Runs once after all tests in this describe block
    console.log('Cleaning up test suite');
  });

  test('test 1', async ({ page }) => {
    // Test implementation
  });

  test('test 2', async ({ page }) => {
    // Test implementation
  });
});

Nested Hooks

test.describe('Parent Suite', () => {
  test.beforeEach(async ({ page }) => {
    console.log('Parent beforeEach');
    await page.goto('https://example.com');
  });

  test.describe('Child Suite 1', () => {
    test.beforeEach(async ({ page }) => {
      console.log('Child 1 beforeEach');
      await page.getByRole('link', { name: 'Products' }).click();
    });

    test('test in child 1', async ({ page }) => {
      // Parent beforeEach runs first, then child beforeEach
    });
  });

  test.describe('Child Suite 2', () => {
    test.beforeEach(async ({ page }) => {
      console.log('Child 2 beforeEach');
      await page.getByRole('link', { name: 'About' }).click();
    });

    test('test in child 2', async ({ page }) => {
      // Parent beforeEach runs first, then child beforeEach
    });
  });
});

Conditional Hooks

test.describe('Feature Tests', () => {
  test.beforeEach(async ({ page, browserName }) => {
    // Skip setup for Firefox
    if (browserName === 'firefox') {
      test.skip();
    }

    await page.goto('https://example.com');
  });

  test.afterEach(async ({ page }, testInfo) => {
    // Only run teardown for failed tests
    if (testInfo.status === 'failed') {
      await page.screenshot({ path: `failure-${testInfo.title}.png` });
    }
  });

  test('feature test', async ({ page }) => {
    // Test implementation
  });
});

Fixture Dependencies

Dependent Fixtures

// fixtures/dependent-fixtures.ts
import { test as base } from '@playwright/test';

type DependentFixtures = {
  config: Config;
  apiClient: ApiClient;
  authenticatedClient: ApiClient;
};

export const test = base.extend<DependentFixtures>({
  // Base fixture
  config: async ({}, use) => {
    const config = {
      apiUrl: process.env.API_URL || 'http://localhost:3000',
      timeout: 30000,
    };
    await use(config);
  },

  // Depends on config
  apiClient: async ({ config }, use) => {
    const client = new ApiClient(config.apiUrl, config.timeout);
    await use(client);
  },

  // Depends on apiClient
  authenticatedClient: async ({ apiClient }, use) => {
    const token = await apiClient.login('user@example.com', 'password');
    apiClient.setAuthToken(token);
    await use(apiClient);
  },
});

Combining Multiple Fixtures

// fixtures/combined-fixtures.ts
import { test as base } from '@playwright/test';

type CombinedFixtures = {
  setupComplete: void;
};

export const test = base.extend<CombinedFixtures>({
  setupComplete: async (
    { page, db, mockApi, testUser },
    use
  ) => {
    // All dependent fixtures are initialized
    await page.goto('https://example.com');
    await page.context().addCookies([
      {
        name: 'userId',
        value: testUser.id.toString(),
        domain: 'example.com',
        path: '/',
      },
    ]);

    await use();
  },
});

Advanced Fixture Patterns

Factory Fixtures

// fixtures/factory-fixtures.ts
import { test as base } from '@playwright/test';

type FactoryFixtures = {
  createUser: (data: Partial<User>) => Promise<User>;
  createProduct: (data: Partial<Product>) => Promise<Product>;
};

export const test = base.extend<FactoryFixtures>({
  createUser: async ({ db }, use) => {
    const users: User[] = [];

    const createUser = async (data: Partial<User>) => {
      const user = await db.user.create({
        data: {
          email: data.email || `user-${Date.now()}@example.com`,
          name: data.name || 'Test User',
          password: data.password || 'password123',
          ...data,
        },
      });
      users.push(user);
      return user;
    };

    await use(createUser);

    // Cleanup: Delete all created users
    for (const user of users) {
      await db.user.delete({ where: { id: user.id } });
    }
  },

  createProduct: async ({ db }, use) => {
    const products: Product[] = [];

    const createProduct = async (data: Partial<Product>) => {
      const product = await db.product.create({
        data: {
          name: data.name || `Product ${Date.now()}`,
          price: data.price || 9.99,
          description: data.description || 'Test product',
          ...data,
        },
      });
      products.push(product);
      return product;
    };

    await use(createProduct);

    // Cleanup: Delete all created products
    for (const product of products) {
      await db.product.delete({ where: { id: product.id } });
    }
  },
});

Option Fixtures

// fixtures/option-fixtures.ts
import { test as base } from '@playwright/test';

type OptionsFixtures = {
  slowNetwork: boolean;
};

export const test = base.extend<OptionsFixtures>({
  slowNetwork: [false, { option: true }],

  page: async ({ page, slowNetwork }, use) => {
    if (slowNetwork) {
      await page.route('**/*', async (route) => {
        await new Promise((resolve) => setTimeout(resolve, 1000));
        await route.continue();
      });
    }

    await use(page);
  },
});
// tests/slow-network.spec.ts
import { test, expect } from '../fixtures/option-fixtures';

test('test with slow network', async ({ page }) => {
  test.use({ slowNetwork: true });

  await page.goto('https://example.com');
  // This will be slow due to network throttling
});

test('test with normal network', async ({ page }) => {
  await page.goto('https://example.com');
  // Normal speed
});

Test Info and Attachments

Using Test Info

test('example with test info', async ({ page }, testInfo) => {
  console.log(`Test title: ${testInfo.title}`);
  console.log(`Project: ${testInfo.project.name}`);
  console.log(`Retry: ${testInfo.retry}`);

  await page.goto('https://example.com');

  // Attach screenshot
  const screenshot = await page.screenshot();
  await testInfo.attach('page-screenshot', {
    body: screenshot,
    contentType: 'image/png',
  });

  // Attach JSON data
  await testInfo.attach('test-data', {
    body: JSON.stringify({ foo: 'bar' }),
    contentType: 'application/json',
  });

  // Attach text
  await testInfo.attach('notes', {
    body: 'Test completed successfully',
    contentType: 'text/plain',
  });
});

Conditional Test Execution

test('browser-specific test', async ({ page, browserName }) => {
  test.skip(browserName === 'webkit', 'Not supported in Safari');

  await page.goto('https://example.com');
  // Test only runs in Chromium and Firefox
});

test('slow test', async ({ page }) => {
  test.slow(); // Triple timeout for this test

  await page.goto('https://slow-site.example.com');
  // Long-running operations
});

test('expected to fail', async ({ page }) => {
  test.fail(); // Mark as expected failure

  await page.goto('https://example.com');
  await expect(page.getByText('Non-existent')).toBeVisible();
});

Fixture Best Practices

Organizing Fixtures

fixtures/
├── index.ts              # Export all fixtures
├── auth-fixtures.ts      # Authentication fixtures
├── database-fixtures.ts  # Database fixtures
├── mock-api-fixtures.ts  # API mocking fixtures
└── page-fixtures.ts      # Page-related fixtures
// fixtures/index.ts
import { test as authTest } from './auth-fixtures';
import { test as dbTest } from './database-fixtures';
import { test as mockTest } from './mock-api-fixtures';

export const test = authTest.extend(dbTest.fixtures).extend(mockTest.fixtures);

export { expect } from '@playwright/test';

Fixture Naming Conventions

// Good naming
export const test = base.extend({
  authenticatedPage: async ({}, use) => { /* ... */ },
  testUser: async ({}, use) => { /* ... */ },
  mockApi: async ({}, use) => { /* ... */ },
});

// Avoid
export const test = base.extend({
  page2: async ({}, use) => { /* ... */ },  // Not descriptive
  data: async ({}, use) => { /* ... */ },   // Too generic
  fixture1: async ({}, use) => { /* ... */ }, // Meaningless name
});

When to Use This Skill

  • Setting up reusable test infrastructure
  • Managing authentication state across tests
  • Creating database seeding and cleanup logic
  • Implementing API mocking for tests
  • Building factory fixtures for test data generation
  • Establishing test lifecycle patterns
  • Creating worker-scoped fixtures for performance
  • Organizing complex test setup and teardown
  • Implementing conditional test behavior
  • Building type-safe fixture systems

Resources

平台分布

Claude Code

26.48%
按下载量换算55

OpenCode

23.1%
按下载量换算48

Codex

15.19%
按下载量换算31

Antigravity

12.97%
按下载量换算27

windsurf

8.09%
按下载量换算17

Cursor

3.12%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills