본문 바로가기

AI

웹페이지에 JavaScript/TypeScript 스크립트를 저장하고, 이를 Playwright로 동적으로 실행하는 아키텍처와 개발 방법

반응형

웹페이지에 JavaScript/TypeScript 스크립트를 저장하고, 이를 Playwright로 동적으로 실행하는 아키텍처와 개발 방법에 대해 단계별로 설명드리겠습니다.


1.목표
> 웹사이트에 JS/TS 스크립트를 저장 → Playwright가 해당 스크립트를 불러와 실행 → 동작 자동화

이 구조는 다음과 같은 시나리오에 유용합니다:
- 스크립트 기반 자동화 플랫폼 개발
- 사용자 정의 테스트 케이스 저장 및 실행
- 로우코드 테스트 도구 구현

---

2. 전체 아키텍처 구성


+------------------+       +---------------------+
|  웹 프론트엔드   |<----->|  백엔드 (API 서버)   |
| (스크립트 저장)  |       | (스크립트 DB 저장)   |
+------------------+       +----------+----------+
                                      |
                                      v
                          +-----------+-----------+
                          |  데이터베이스 (MongoDB, PostgreSQL 등) |
                          |  - 스크립트 코드 저장                 |
                          +------------------------+

                                      |
                                      v
                   +----------------------------------+
                   |  Playwright 실행 엔진 (Node.js)   |
                   |  - DB에서 스크립트 읽어와 실행     |
                   +----------------------------------+
3. 주요 구성 요소

| 구성 요소 | 역할 |
|---------|------|
| 프론트엔드 (Web UI) | 사용자가 JS/TS 스크립트를 작성, 저장, 편집 |
| 백엔드 API 서버 | 스크립트 저장/수정/삭제, Playwright 실행 요청 |
| DB | 사용자 스크립트 저장 (예: `scriptId`, `code`, `name`, `createdAt`) |
| Playwright Worker | 저장된 스크립트를 동적으로 실행하는 Node.js 프로세스 |



예시: 데이터베이스 스키마 (PostgreSQL 기준)


CREATE TABLE scripts (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100),
  code TEXT NOT NULL,           -- 사용자가 작성한 JS/TS 코드
  language VARCHAR(10) DEFAULT 'javascript', -- 'javascript' or 'typescript'
  created_at TIMESTAMP DEFAULT NOW()
);


예시 데이터:
json
{
  "id": 1,
  "name": "Login Test",
  "code": "await page.goto('https://example.com'); await page.screenshot({ path: 'login.png' });",
  "language": "javascript"
}




##  개발 방법 단계별 설명

---

### 1. ? 프론트엔드: 스크립트 저장 UI 만들기

- 도구: React, Vue, Angular 등
- 기능:
  - 코드 에디터 (예: [Monaco Editor](https://microsoft.github.io/monaco-editor/) ? VS Code 엔진)
  - 저장/실행 버튼
  - 스크립트 목록 표시

tsx
// 예: React + Monaco Editor
import Editor from '@monaco-editor/react';

function ScriptEditor() {
  const [code, setCode] = useState(`await page.goto('https://example.com');`);

  const handleSave = () => {
    fetch('/api/scripts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name: 'Test', code, language: 'javascript' })
    });
  };

  return (
    <div>
      <Editor
        height="500px"
        defaultLanguage="javascript"
        defaultValue={code}
        onChange={setCode}
      />
      <button onClick={handleSave}>저장</button>
      <button onClick={() => runScript(code)}>실행 (Playwright)</button>
    </div>
  );
}


---

### 2.  백엔드: API 서버 (Node.js + Express)

javascript
// server.js
const express = require('express');
const { Pool } = require('pg');
const { execScript } = require('./playwright-runner');

const app = express();
app.use(express.json());

const pool = new Pool({ connectionString: 'your-db-url' });

// 스크립트 저장
app.post('/api/scripts', async (req, res) => {
  const { name, code, language } = req.body;
  const result = await pool.query(
    'INSERT INTO scripts (name, code, language) VALUES ($1, $2, $3) RETURNING id',
    [name, code, language]
  );
  res.json({ id: result.rows[0].id });
});

// 스크립트 실행 요청
app.post('/api/scripts/run/:id', async (req, res) => {
  const { id } = req.params;
  const result = await pool.query('SELECT code FROM scripts WHERE id = $1', [id]);
  if (!result.rows[0]) return res.status(404).send('Script not found');

  try {
    const output = await execScript(result.rows[0].code);  // Playwright 실행
    res.json({ success: true, output });
  } catch (error) {
    res.json({ success: false, error: error.message });
  }
});

app.listen(3001, () => console.log('Server running on 3001'));


---

### 3.  Playwright 실행기 (`playwright-runner.js`)

이 파일은 저장된 코드를 안전하게 실행합니다.

javascript
// playwright-runner.js
const { chromium } = require('playwright');

let browser;

// 브라우저 재사용 (성능 향상)
async function getBrowser() {
  if (!browser) {
    browser = await chromium.launch({ headless: true });
  }
  return browser;
}

// 외부 코드 실행 함수
async function execScript(code) {
  const browser = await getBrowser();
  const context = await browser.newContext();
  const page = await context.newPage();

  let result;

  // 주의: eval()은 보안 위험! 하지만 여기서는 제한된 환경에서만 사용
  // 보안을 강화하려면 코드 샌드박스화 (예: vm2) 필요
  try {
    // page 객체를 주입하여 사용자가 page를 사용할 수 있도록 함
    const func = new Function('page', code);  // 주의: 보안 취약점 존재
    result = await func(page);
    await context.close();
    return { result };
  } catch (error) {
    await context.close();
    throw error;
  }
}

module.exports = { execScript, getBrowser };


>  보안 주의: `new Function()` 또는 `eval()`은 XSS 및 코드 주입 위험이 있음.  
> 프로덕션에서는 [vm2](https://github.com/patriksimek/vm2) 같은 샌드박스 도구를 사용하세요.


4. 보안 강화 (선택 사항)

#### 샌드박스 예시 (vm2 사용)
bash
npm install vm2


javascript
const { NodeVM } = require('vm2');

async function execScriptSecure(code) {
  const browser = await getBrowser();
  const context = await browser.newContext();
  const page = await context.newPage();

  const vm = new NodeVM({
    sandbox: { page },  // page 객체만 허용
    require: {
      external: false,  // 외부 모듈 차단
      builtin: []       // 허용된 내장 모듈 제한
    }
  });

  try {
    await vm.run(`
      (async () => {
        ${code}
      })();
    `, __filename);
    await context.close();
    return { success: true };
  } catch (error) {
    await context.close();
    throw error;
  }
}


---

##  실행 흐름 요약

1. 사용자가 웹 UI에서 JS 코드 작성 → 저장
2. 백엔드가 DB에 코드 저장
3. 사용자가 "실행" 버튼 클릭
4. 백엔드가 DB에서 코드 가져와 Playwright 실행기 호출
5. Playwright가 브라우저 열고, 코드 실행
6. 결과 (성공/스크린샷/에러) 반환 → 프론트엔드 표시

---

##  폴더 구조 예시


/project-root

├── frontend/             # React 프론트엔드
├── backend/
│   ├── server.js         # Express API
│   ├── playwright-runner.js
│   └── db.js
├── database/
│   └── schema.sql
└── package.json


---

## ? 활용 사례

- 자동화 테스트 플랫폼 (사용자 정의 스크립트 저장/실행)
- 로우코드 웹 자동화 도구 (코드 기반 + UI 기반 혼합)
- 스크래핑 규칙 저장소
- 교육용 플랫폼 (학생이 Playwright 코드 작성 후 실행)

---

##  주의사항

| 항목 | 설명 |
|------|------|
|  보안 | 사용자 입력 코드 실행은 매우 위험. 반드시 샌드박스 적용 |
|  격리 | 각 스크립트는 독립된 `context`에서 실행 (쿠키/세션 격리) |
| ? 타임아웃 | 무한 루프 방지를 위해 `Promise.race` + 타임아웃 설정 |
|  로깅 | 실행 로그, 스크린샷, 에러 기록 필수 |

---

## ? 보너스: TypeScript 지원

- 백엔드를 TypeScript로 만들면 더 안정적
- 사용자가 저장한 `.ts` 코드는 실행 전 컴파일 필요 없음 (Playwright는 JS만 실행)
- 따라서 저장 시 TS → JS 변환 필요:

bash
npm install typescript


javascript
const ts = require('typescript');

function transpile(tsCode) {
  return ts.transpile(tsCode, {
    target: ts.ScriptTarget.ES2020,
    module: ts.ModuleKind.CommonJS
  });
}


---

##  결론

> ? 웹에 저장된 JS/TS 스크립트를 Playwright로 실행하는 시스템은  
> 자동화 플랫폼, 테스트 관리 도구, 로우코드 솔루션에 매우 유용합니다.

### 추천 스택 조합
- 프론트엔드: React + Monaco Editor
- 백엔드: Node.js + Express
- DB: PostgreSQL or MongoDB
- 실행: Playwright + vm2 (보안 샌드박스)

---

반응형