All skills
simota avatar

/vector

@e307415
by shingo imotasimota/agent-skills85 stars
15

Automating browsers via Playwright and Chrome DevTools for data collection, form interaction, screenshot capture, and network monitoring. Task completion focus (vs Voyager for E2E testing).

Use this Skill: https://skilld.dev/gh/simota/agent-skills/vector

This session only. Nothing lands on disk.

referencedata-extraction.md

≈2.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Vector Data Extraction Patterns

Patterns for extracting, validating, and processing web data.


TEXT CONTENT EXTRACTION

// Single element
const title = await page.locator('h1').textContent();

// Multiple elements
const items = await page.locator('.list-item').allTextContents();

// With filtering
const prices = await page.locator('.price')
  .filter({ hasText: /\d+/ })
  .allTextContents();

STRUCTURED DATA EXTRACTION

// Product list
const products = await page.evaluate(() => {
  return Array.from(document.querySelectorAll('.product-card')).map(card => ({
    name: card.querySelector('.product-name')?.textContent?.trim(),
    price: card.querySelector('.product-price')?.textContent?.trim(),
    rating: card.querySelector('.product-rating')?.getAttribute('data-rating'),
    imageUrl: card.querySelector('img')?.src,
    productUrl: card.querySelector('a')?.href,
  }));
});

TABLE DATA EXTRACTION

// Extract table to JSON
const tableData = await page.evaluate(() => {
  const table = document.querySelector('table');
  const headers = Array.from(table.querySelectorAll('thead th'))
    .map(th => th.textContent?.trim() || '');

  const rows = Array.from(table.querySelectorAll('tbody tr')).map(row => {
    const cells = Array.from(row.querySelectorAll('td'));
    return headers.reduce((obj, header, index) => {
      obj[header] = cells[index]?.textContent?.trim() || '';
      return obj;
    }, {} as Record<string, string>);
  });

  return rows;
});

PAGINATION HANDLING

// Collect data across multiple pages
async function collectPaginatedData(page: Page): Promise<any[]> {
  const allData: any[] = [];
  let hasNextPage = true;
  let pageNum = 1;

  while (hasNextPage) {
    // Extract data from current page
    const pageData = await extractPageData(page);
    allData.push(...pageData);

    // Screenshot current page
    await page.screenshot({
      path: `.vector/screenshots/page_${pageNum}.png`
    });

    // Check for next page
    const nextButton = page.locator('[data-testid="next-page"]');
    hasNextPage = await nextButton.isVisible() && await nextButton.isEnabled();

    if (hasNextPage) {
      await nextButton.click();
      await page.waitForLoadState('networkidle');
      pageNum++;
    }
  }

  return allData;
}

DATA VALIDATION

// Validate extracted data
function validateData(data: any[]): ValidationResult {
  const errors: string[] = [];
  const warnings: string[] = [];

  data.forEach((item, index) => {
    // Required fields
    if (!item.name) errors.push(`Row ${index}: Missing name`);
    if (!item.price) errors.push(`Row ${index}: Missing price`);

    // Format validation
    if (item.price && !/^\d+(\.\d{2})?$/.test(item.price)) {
      warnings.push(`Row ${index}: Price format irregular`);
    }

    // Duplicate detection
    const duplicates = data.filter(d => d.name === item.name);
    if (duplicates.length > 1) {
      warnings.push(`Row ${index}: Duplicate name detected`);
    }
  });

  return {
    valid: errors.length === 0,
    errors,
    warnings,
    recordCount: data.length,
  };
}

FORM OPERATIONS

Form Analysis

// Analyze form structure
async function analyzeForm(page: Page, formSelector: string) {
  return await page.evaluate((selector) => {
    const form = document.querySelector(selector);
    if (!form) return null;

    const fields = Array.from(form.querySelectorAll('input, select, textarea'));
    return fields.map(field => ({
      name: field.getAttribute('name'),
      type: field.getAttribute('type') || field.tagName.toLowerCase(),
      required: field.hasAttribute('required'),
      placeholder: field.getAttribute('placeholder'),
      options: field.tagName === 'SELECT'
        ? Array.from(field.querySelectorAll('option')).map(o => o.value)
        : undefined,
    }));
  }, formSelector);
}

Form Filling

// Fill form with validation
async function fillForm(page: Page, formData: Record<string, string>) {
  for (const [name, value] of Object.entries(formData)) {
    const field = page.locator(`[name="${name}"]`);
    const tagName = await field.evaluate(el => el.tagName);
    const inputType = await field.getAttribute('type');

    if (tagName === 'SELECT') {
      await field.selectOption(value);
    } else if (inputType === 'checkbox') {
      if (value === 'true') await field.check();
      else await field.uncheck();
    } else if (inputType === 'radio') {
      await page.locator(`[name="${name}"][value="${value}"]`).check();
    } else if (inputType === 'file') {
      await field.setInputFiles(value);
    } else {
      await field.fill(value);
    }

    // Brief pause to allow validation
    await page.waitForTimeout(100);
  }
}

Form Submission (with confirmation)

// Submit form with safeguards
async function submitForm(page: Page, formSelector: string): Promise<SubmitResult> {
  // Capture form state before submission
  const preSubmitScreenshot = `.vector/screenshots/form_pre_submit.png`;
  await page.screenshot({ path: preSubmitScreenshot });

  // Get form data for logging
  const formData = await page.evaluate((selector) => {
    const form = document.querySelector(selector) as HTMLFormElement;
    return Object.fromEntries(new FormData(form));
  }, formSelector);

  // Log submission attempt
  console.log('Form submission:', JSON.stringify(formData, null, 2));

  // Submit and wait for response
  const [response] = await Promise.all([
    page.waitForNavigation({ waitUntil: 'networkidle' }),
    page.locator(`${formSelector} [type="submit"]`).click(),
  ]);

  // Capture result
  const postSubmitScreenshot = `.vector/screenshots/form_post_submit.png`;
  await page.screenshot({ path: postSubmitScreenshot });

  return {
    success: response?.ok() ?? false,
    status: response?.status(),
    url: page.url(),
    screenshots: {
      before: preSubmitScreenshot,
      after: postSubmitScreenshot,
    },
  };
}

AUTHENTICATION HANDLING

Session State Management

// Save authentication state
async function saveAuthState(context: BrowserContext, path: string) {
  await context.storageState({ path });
  console.log(`Auth state saved to: ${path}`);
}

// Load authentication state
async function loadAuthState(browser: Browser, statePath: string) {
  const context = await browser.newContext({
    storageState: statePath,
  });
  return context;
}

Login Flow

// Execute login flow
async function login(page: Page): Promise<boolean> {
  const email = process.env.NAVIGATOR_USER_EMAIL;
  const password = process.env.NAVIGATOR_USER_PASSWORD;

  if (!email || !password) {
    throw new Error('Authentication credentials not found in environment variables');
  }

  // Navigate to login page
  await page.goto('/login');

  // Fill credentials
  await page.fill('[data-testid="email-input"]', email);
  await page.fill('[data-testid="password-input"]', password);

  // Submit
  await page.click('[data-testid="login-submit"]');

  // Wait for redirect or success indicator
  try {
    await page.waitForURL('**/dashboard', { timeout: 10000 });
    return true;
  } catch {
    // Check for error message
    const errorVisible = await page.locator('[data-testid="login-error"]').isVisible();
    if (errorVisible) {
      const errorText = await page.locator('[data-testid="login-error"]').textContent();
      console.error('Login failed:', errorText);
    }
    return false;
  }
}

Authentication State Check

// Check if currently authenticated
async function isAuthenticated(page: Page): Promise<boolean> {
  // Method 1: Check for user menu/avatar
  const userMenuVisible = await page.locator('[data-testid="user-menu"]').isVisible();
  if (userMenuVisible) return true;

  // Method 2: Check cookies
  const cookies = await page.context().cookies();
  const authCookie = cookies.find(c => c.name === 'session' || c.name === 'auth_token');
  if (authCookie) return true;

  // Method 3: Check localStorage
  const token = await page.evaluate(() => localStorage.getItem('auth_token'));
  if (token) return true;

  return false;
}

ERROR HANDLING

Error Classification

Error Type Cause Action
ElementNotFound Selector changed/invalid Update selector, retry
Timeout Page slow, element hidden Increase timeout, check visibility
NavigationFailed Invalid URL, blocked Verify URL, check access
NetworkError Connection issue Retry with backoff
AuthenticationFailed Credentials invalid Escalate to user
RateLimited Too many requests Wait and retry
CAPTCHABlocked CAPTCHA present Escalate to user

Error Recovery with Retry

// Retry with exponential backoff
async function retryWithBackoff<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  let lastError: Error;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }

  throw lastError!;
}

Error Logging

// Log error with context
function logError(error: Error, context: ErrorContext) {
  const errorLog = {
    timestamp: new Date().toISOString(),
    type: error.name,
    message: error.message,
    stack: error.stack,
    context: {
      url: context.url,
      step: context.step,
      selector: context.selector,
      screenshot: context.screenshotPath,
    },
  };

  fs.appendFileSync(
    '.vector/logs/errors.json',
    JSON.stringify(errorLog) + '\n'
  );
}

Cross-Reference Links

Reference Content
execution-templates.md Execution phase templates, wait strategies, error handling
playwright-cdp.md MCP server operations, CDP methods, connection patterns

Source: SKILL.md on GitHub

1 alert13d3 checks · Risk SAFE
  • Gen Agent Trust Hub13d

    This skill provides advanced browser automation and web crawling capabilities, including techniques for bypassing bot detection. While feature-rich and documented with ethical guidelines, it presents an inherent risk of indirect prompt injection due to its core function of processing untrusted web content and provides tools for dynamic code execution within browser environments.

  • Socket13d

    3 alerts: gptAnomaly, gptMalware

  • Snyk13d

    Risk: MEDIUM · 1 issue

Signed by skilld at e307415. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 2 weeks ago

README badge

README badge for simota/agent-skills/vector