notelm-mcp 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +456 -0
- package/dist/auth/auth-manager.d.ts +139 -0
- package/dist/auth/auth-manager.d.ts.map +1 -0
- package/dist/auth/auth-manager.js +960 -0
- package/dist/auth/auth-manager.js.map +1 -0
- package/dist/config.d.ts +92 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +219 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +58 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +133 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +325 -0
- package/dist/index.js.map +1 -0
- package/dist/library/notebook-library.d.ts +70 -0
- package/dist/library/notebook-library.d.ts.map +1 -0
- package/dist/library/notebook-library.js +279 -0
- package/dist/library/notebook-library.js.map +1 -0
- package/dist/library/types.d.ts +67 -0
- package/dist/library/types.d.ts.map +1 -0
- package/dist/library/types.js +8 -0
- package/dist/library/types.js.map +1 -0
- package/dist/playwright.config.d.ts +3 -0
- package/dist/playwright.config.d.ts.map +1 -0
- package/dist/playwright.config.js +38 -0
- package/dist/playwright.config.js.map +1 -0
- package/dist/resources/resource-handlers.d.ts +22 -0
- package/dist/resources/resource-handlers.d.ts.map +1 -0
- package/dist/resources/resource-handlers.js +216 -0
- package/dist/resources/resource-handlers.js.map +1 -0
- package/dist/scripts/save-auth-state.d.ts +2 -0
- package/dist/scripts/save-auth-state.d.ts.map +1 -0
- package/dist/scripts/save-auth-state.js +91 -0
- package/dist/scripts/save-auth-state.js.map +1 -0
- package/dist/session/browser-session.d.ts +108 -0
- package/dist/session/browser-session.d.ts.map +1 -0
- package/dist/session/browser-session.js +636 -0
- package/dist/session/browser-session.js.map +1 -0
- package/dist/session/session-manager.d.ts +76 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +273 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/shared-context-manager.d.ts +107 -0
- package/dist/session/shared-context-manager.d.ts.map +1 -0
- package/dist/session/shared-context-manager.js +447 -0
- package/dist/session/shared-context-manager.js.map +1 -0
- package/dist/src/auth/auth-manager.d.ts +139 -0
- package/dist/src/auth/auth-manager.d.ts.map +1 -0
- package/dist/src/auth/auth-manager.js +960 -0
- package/dist/src/auth/auth-manager.js.map +1 -0
- package/dist/src/config.d.ts +92 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +219 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/constants.d.ts +58 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +133 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +26 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +41 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +325 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/library/notebook-library.d.ts +70 -0
- package/dist/src/library/notebook-library.d.ts.map +1 -0
- package/dist/src/library/notebook-library.js +279 -0
- package/dist/src/library/notebook-library.js.map +1 -0
- package/dist/src/library/types.d.ts +67 -0
- package/dist/src/library/types.d.ts.map +1 -0
- package/dist/src/library/types.js +8 -0
- package/dist/src/library/types.js.map +1 -0
- package/dist/src/resources/resource-handlers.d.ts +22 -0
- package/dist/src/resources/resource-handlers.d.ts.map +1 -0
- package/dist/src/resources/resource-handlers.js +216 -0
- package/dist/src/resources/resource-handlers.js.map +1 -0
- package/dist/src/scripts/health-check.d.ts +13 -0
- package/dist/src/scripts/health-check.d.ts.map +1 -0
- package/dist/src/scripts/health-check.js +100 -0
- package/dist/src/scripts/health-check.js.map +1 -0
- package/dist/src/session/browser-session.d.ts +108 -0
- package/dist/src/session/browser-session.d.ts.map +1 -0
- package/dist/src/session/browser-session.js +642 -0
- package/dist/src/session/browser-session.js.map +1 -0
- package/dist/src/session/session-manager.d.ts +76 -0
- package/dist/src/session/session-manager.d.ts.map +1 -0
- package/dist/src/session/session-manager.js +273 -0
- package/dist/src/session/session-manager.js.map +1 -0
- package/dist/src/session/shared-context-manager.d.ts +107 -0
- package/dist/src/session/shared-context-manager.d.ts.map +1 -0
- package/dist/src/session/shared-context-manager.js +447 -0
- package/dist/src/session/shared-context-manager.js.map +1 -0
- package/dist/src/tools/definitions/ask-question.d.ts +8 -0
- package/dist/src/tools/definitions/ask-question.d.ts.map +1 -0
- package/dist/src/tools/definitions/ask-question.js +211 -0
- package/dist/src/tools/definitions/ask-question.js.map +1 -0
- package/dist/src/tools/definitions/notebook-management.d.ts +3 -0
- package/dist/src/tools/definitions/notebook-management.d.ts.map +1 -0
- package/dist/src/tools/definitions/notebook-management.js +243 -0
- package/dist/src/tools/definitions/notebook-management.js.map +1 -0
- package/dist/src/tools/definitions/session-management.d.ts +3 -0
- package/dist/src/tools/definitions/session-management.d.ts.map +1 -0
- package/dist/src/tools/definitions/session-management.js +41 -0
- package/dist/src/tools/definitions/session-management.js.map +1 -0
- package/dist/src/tools/definitions/system.d.ts +3 -0
- package/dist/src/tools/definitions/system.d.ts.map +1 -0
- package/dist/src/tools/definitions/system.js +143 -0
- package/dist/src/tools/definitions/system.js.map +1 -0
- package/dist/src/tools/definitions.d.ts +12 -0
- package/dist/src/tools/definitions.d.ts.map +1 -0
- package/dist/src/tools/definitions.js +26 -0
- package/dist/src/tools/definitions.js.map +1 -0
- package/dist/src/tools/handlers.d.ts +212 -0
- package/dist/src/tools/handlers.d.ts.map +1 -0
- package/dist/src/tools/handlers.js +712 -0
- package/dist/src/tools/handlers.js.map +1 -0
- package/dist/src/tools/index.d.ts +8 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +8 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/auth-manager.d.ts +2 -0
- package/dist/src/utils/auth-manager.d.ts.map +1 -0
- package/dist/src/utils/auth-manager.js +25 -0
- package/dist/src/utils/auth-manager.js.map +1 -0
- package/dist/src/utils/cleanup-manager.d.ts +133 -0
- package/dist/src/utils/cleanup-manager.d.ts.map +1 -0
- package/dist/src/utils/cleanup-manager.js +673 -0
- package/dist/src/utils/cleanup-manager.js.map +1 -0
- package/dist/src/utils/cli-handler.d.ts +16 -0
- package/dist/src/utils/cli-handler.d.ts.map +1 -0
- package/dist/src/utils/cli-handler.js +102 -0
- package/dist/src/utils/cli-handler.js.map +1 -0
- package/dist/src/utils/logger.d.ts +61 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +92 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/page-utils.d.ts +54 -0
- package/dist/src/utils/page-utils.d.ts.map +1 -0
- package/dist/src/utils/page-utils.js +381 -0
- package/dist/src/utils/page-utils.js.map +1 -0
- package/dist/src/utils/rate-limit-handler.d.ts +42 -0
- package/dist/src/utils/rate-limit-handler.d.ts.map +1 -0
- package/dist/src/utils/rate-limit-handler.js +88 -0
- package/dist/src/utils/rate-limit-handler.js.map +1 -0
- package/dist/src/utils/rate-limit-handler.test.d.ts +7 -0
- package/dist/src/utils/rate-limit-handler.test.d.ts.map +1 -0
- package/dist/src/utils/rate-limit-handler.test.js +86 -0
- package/dist/src/utils/rate-limit-handler.test.js.map +1 -0
- package/dist/src/utils/settings-manager.d.ts +37 -0
- package/dist/src/utils/settings-manager.d.ts.map +1 -0
- package/dist/src/utils/settings-manager.js +121 -0
- package/dist/src/utils/settings-manager.js.map +1 -0
- package/dist/src/utils/stealth-utils.d.ts +135 -0
- package/dist/src/utils/stealth-utils.d.ts.map +1 -0
- package/dist/src/utils/stealth-utils.js +396 -0
- package/dist/src/utils/stealth-utils.js.map +1 -0
- package/dist/src/utils/stealth-utils.test.d.ts +7 -0
- package/dist/src/utils/stealth-utils.test.d.ts.map +1 -0
- package/dist/src/utils/stealth-utils.test.js +72 -0
- package/dist/src/utils/stealth-utils.test.js.map +1 -0
- package/dist/tests/e2e/authenticated.spec.d.ts +2 -0
- package/dist/tests/e2e/authenticated.spec.d.ts.map +1 -0
- package/dist/tests/e2e/authenticated.spec.js +41 -0
- package/dist/tests/e2e/authenticated.spec.js.map +1 -0
- package/dist/tests/e2e/mocked.spec.d.ts +2 -0
- package/dist/tests/e2e/mocked.spec.d.ts.map +1 -0
- package/dist/tests/e2e/mocked.spec.js +32 -0
- package/dist/tests/e2e/mocked.spec.js.map +1 -0
- package/dist/tests/mocks/handlers.d.ts +4 -0
- package/dist/tests/mocks/handlers.d.ts.map +1 -0
- package/dist/tests/mocks/handlers.js +55 -0
- package/dist/tests/mocks/handlers.js.map +1 -0
- package/dist/tests/mocks/setup.d.ts +3 -0
- package/dist/tests/mocks/setup.d.ts.map +1 -0
- package/dist/tests/mocks/setup.js +77 -0
- package/dist/tests/mocks/setup.js.map +1 -0
- package/dist/tools/definitions/ask-question.d.ts +8 -0
- package/dist/tools/definitions/ask-question.d.ts.map +1 -0
- package/dist/tools/definitions/ask-question.js +211 -0
- package/dist/tools/definitions/ask-question.js.map +1 -0
- package/dist/tools/definitions/notebook-management.d.ts +3 -0
- package/dist/tools/definitions/notebook-management.d.ts.map +1 -0
- package/dist/tools/definitions/notebook-management.js +243 -0
- package/dist/tools/definitions/notebook-management.js.map +1 -0
- package/dist/tools/definitions/session-management.d.ts +3 -0
- package/dist/tools/definitions/session-management.d.ts.map +1 -0
- package/dist/tools/definitions/session-management.js +41 -0
- package/dist/tools/definitions/session-management.js.map +1 -0
- package/dist/tools/definitions/system.d.ts +3 -0
- package/dist/tools/definitions/system.d.ts.map +1 -0
- package/dist/tools/definitions/system.js +143 -0
- package/dist/tools/definitions/system.js.map +1 -0
- package/dist/tools/definitions.d.ts +12 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +26 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers.d.ts +212 -0
- package/dist/tools/handlers.d.ts.map +1 -0
- package/dist/tools/handlers.js +712 -0
- package/dist/tools/handlers.js.map +1 -0
- package/dist/tools/index.d.ts +8 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cleanup-manager.d.ts +133 -0
- package/dist/utils/cleanup-manager.d.ts.map +1 -0
- package/dist/utils/cleanup-manager.js +673 -0
- package/dist/utils/cleanup-manager.js.map +1 -0
- package/dist/utils/cli-handler.d.ts +16 -0
- package/dist/utils/cli-handler.d.ts.map +1 -0
- package/dist/utils/cli-handler.js +102 -0
- package/dist/utils/cli-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +61 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +92 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/page-utils.d.ts +54 -0
- package/dist/utils/page-utils.d.ts.map +1 -0
- package/dist/utils/page-utils.js +381 -0
- package/dist/utils/page-utils.js.map +1 -0
- package/dist/utils/rate-limit-handler.d.ts +42 -0
- package/dist/utils/rate-limit-handler.d.ts.map +1 -0
- package/dist/utils/rate-limit-handler.js +88 -0
- package/dist/utils/rate-limit-handler.js.map +1 -0
- package/dist/utils/rate-limit-handler.test.d.ts +7 -0
- package/dist/utils/rate-limit-handler.test.d.ts.map +1 -0
- package/dist/utils/rate-limit-handler.test.js +91 -0
- package/dist/utils/rate-limit-handler.test.js.map +1 -0
- package/dist/utils/settings-manager.d.ts +37 -0
- package/dist/utils/settings-manager.d.ts.map +1 -0
- package/dist/utils/settings-manager.js +121 -0
- package/dist/utils/settings-manager.js.map +1 -0
- package/dist/utils/stealth-utils.d.ts +135 -0
- package/dist/utils/stealth-utils.d.ts.map +1 -0
- package/dist/utils/stealth-utils.js +396 -0
- package/dist/utils/stealth-utils.js.map +1 -0
- package/dist/utils/stealth-utils.test.d.ts +7 -0
- package/dist/utils/stealth-utils.test.d.ts.map +1 -0
- package/dist/utils/stealth-utils.test.js +72 -0
- package/dist/utils/stealth-utils.test.js.map +1 -0
- package/docs/01_configuration.md +94 -0
- package/docs/02_tools.md +34 -0
- package/docs/03_troubleshooting.md +59 -0
- package/docs/04_usage-guide.md +245 -0
- package/docs/05_project-analysis.qmd +309 -0
- package/docs/06_integration-analysis.html +914 -0
- package/docs/06_integration-analysis.qmd +255 -0
- package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap-4f0954b6b0dd6bf39f4bb9151ba984db.min.css +12 -0
- package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap-icons.css +2106 -0
- package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap-icons.woff +0 -0
- package/docs/06_integration-analysis_files/libs/bootstrap/bootstrap.min.js +7 -0
- package/docs/06_integration-analysis_files/libs/clipboard/clipboard.min.js +7 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/anchor.min.js +9 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/axe/axe-check.js +145 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/popper.min.js +6 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/quarto.js +847 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/tabsets/tabsets.js +95 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/tippy.css +1 -0
- package/docs/06_integration-analysis_files/libs/quarto-html/tippy.umd.min.js +2 -0
- package/docs/07_e2e-testing-safety.qmd +754 -0
- package/docs/08_project-re-evaluation.html +609 -0
- package/docs/08_project-re-evaluation.qmd +86 -0
- package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap-4f0954b6b0dd6bf39f4bb9151ba984db.min.css +12 -0
- package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap-icons.css +2106 -0
- package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap-icons.woff +0 -0
- package/docs/08_project-re-evaluation_files/libs/bootstrap/bootstrap.min.js +7 -0
- package/docs/08_project-re-evaluation_files/libs/clipboard/clipboard.min.js +7 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/anchor.min.js +9 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/axe/axe-check.js +145 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/popper.min.js +6 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/quarto.js +847 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/tabsets/tabsets.js +95 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/tippy.css +1 -0
- package/docs/08_project-re-evaluation_files/libs/quarto-html/tippy.umd.min.js +2 -0
- package/docs/notebooklm-mcp-usage.html +704 -0
- package/docs/notebooklm-mcp-usage.qmd +119 -0
- package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap-6b71f2156b6a5230c6677325978bcf08.min.css +12 -0
- package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap-icons.css +2106 -0
- package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap-icons.woff +0 -0
- package/docs/notebooklm-mcp-usage_files/libs/bootstrap/bootstrap.min.js +7 -0
- package/docs/notebooklm-mcp-usage_files/libs/clipboard/clipboard.min.js +7 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/anchor.min.js +9 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/axe/axe-check.js +145 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/popper.min.js +6 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/quarto.js +847 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/tabsets/tabsets.js +95 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/tippy.css +1 -0
- package/docs/notebooklm-mcp-usage_files/libs/quarto-html/tippy.umd.min.js +2 -0
- package/docs/repomix-usage_files/libs/bootstrap/bootstrap-6b71f2156b6a5230c6677325978bcf08.min.css +12 -0
- package/docs/repomix-usage_files/libs/bootstrap/bootstrap-icons.css +2106 -0
- package/docs/repomix-usage_files/libs/bootstrap/bootstrap-icons.woff +0 -0
- package/docs/repomix-usage_files/libs/bootstrap/bootstrap.min.js +7 -0
- package/docs/repomix-usage_files/libs/clipboard/clipboard.min.js +7 -0
- package/docs/repomix-usage_files/libs/quarto-html/anchor.min.js +9 -0
- package/docs/repomix-usage_files/libs/quarto-html/axe/axe-check.js +145 -0
- package/docs/repomix-usage_files/libs/quarto-html/popper.min.js +6 -0
- package/docs/repomix-usage_files/libs/quarto-html/quarto-syntax-highlighting-587c61ba64f3a5504c4d52d930310e48.css +236 -0
- package/docs/repomix-usage_files/libs/quarto-html/quarto.js +847 -0
- package/docs/repomix-usage_files/libs/quarto-html/tabsets/tabsets.js +95 -0
- package/docs/repomix-usage_files/libs/quarto-html/tippy.css +1 -0
- package/docs/repomix-usage_files/libs/quarto-html/tippy.umd.min.js +2 -0
- package/package.json +62 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "E2E 테스트: Google 자동화 감지 회피 전략"
|
|
3
|
+
subtitle: "안전한 브라우저 자동화 테스트를 위한 종합 연구 가이드"
|
|
4
|
+
author: "NotebookLM MCP Team"
|
|
5
|
+
date: 2026-01-02
|
|
6
|
+
format:
|
|
7
|
+
html:
|
|
8
|
+
toc: true
|
|
9
|
+
toc-depth: 3
|
|
10
|
+
number-sections: true
|
|
11
|
+
highlight-style: github
|
|
12
|
+
code-fold: false
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 요약
|
|
16
|
+
|
|
17
|
+
Google의 자동화 감지 시스템은 매우 정교하며, 무분별한 자동화 테스트는 계정 정지나 차단으로 이어질 수 있습니다. 본 문서는 안전한 E2E 테스트를 위한 전략을 4단계로 분류하여 제시합니다.
|
|
18
|
+
|
|
19
|
+
> [!CAUTION]
|
|
20
|
+
> **계정 보호가 최우선입니다.** 실제 Google 계정으로 자동화 로그인을 시도하면 계정이 정지될 수 있습니다.
|
|
21
|
+
|
|
22
|
+
### 권장 전략 우선순위
|
|
23
|
+
|
|
24
|
+
| 순위 | 전략 | 계정 위험도 | 구현 복잡도 | 테스트 신뢰도 |
|
|
25
|
+
|:---:|:---|:---:|:---:|:---:|
|
|
26
|
+
| 1 | 완전 모킹 (LoFi Mock) | ⭐ 0% | 중간 | 높음 |
|
|
27
|
+
| 2 | 사전인증 세션 재사용 | ⭐ 5% | 낮음 | 매우 높음 |
|
|
28
|
+
| 3 | Patchright 스텔스 | ⚠️ 20% | 낮음 | 높음 |
|
|
29
|
+
| 4 | 실제 인증 자동화 | 🔴 높음 | 낮음 | 매우 높음 |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Google 자동화 감지 메커니즘
|
|
34
|
+
|
|
35
|
+
Google은 브라우저 자동화를 감지하기 위해 다중 계층 방어 시스템을 사용합니다.
|
|
36
|
+
|
|
37
|
+
### 1단계: 브라우저 핑거프린트 분석
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// Google이 감지하는 주요 자동화 신호
|
|
41
|
+
const detectionSignals = {
|
|
42
|
+
// 1. WebDriver 플래그
|
|
43
|
+
navigatorWebdriver: navigator.webdriver, // true면 자동화
|
|
44
|
+
|
|
45
|
+
// 2. User-Agent 문자열
|
|
46
|
+
headlessChrome: navigator.userAgent.includes('HeadlessChrome'),
|
|
47
|
+
|
|
48
|
+
// 3. Chrome DevTools Protocol (CDP) 사용
|
|
49
|
+
runtimeEnabled: typeof Runtime !== 'undefined', // CDP 활성화
|
|
50
|
+
|
|
51
|
+
// 4. 자동화 관련 플래그
|
|
52
|
+
automationControlled: window.navigator.plugins.length === 0,
|
|
53
|
+
|
|
54
|
+
// 5. Chrome 객체 부재
|
|
55
|
+
chromeAppMissing: !window.chrome?.app,
|
|
56
|
+
chromeCsiMissing: !window.chrome?.csi,
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2단계: 행동 패턴 분석
|
|
61
|
+
|
|
62
|
+
| 감지 항목 | 정상 사용자 | 자동화 봇 |
|
|
63
|
+
|:---|:---|:---|
|
|
64
|
+
| 마우스 이동 | 곡선, 불규칙 | 직선, 순간이동 |
|
|
65
|
+
| 타이핑 속도 | 50-150 WPM, 오타 포함 | 매우 빠름, 일정 |
|
|
66
|
+
| 페이지 탐색 | 스크롤, 읽기 시간 | 즉시 대상 요소 접근 |
|
|
67
|
+
| 클릭 간격 | 불규칙 (0.5-3초) | 일정한 고정 값 |
|
|
68
|
+
| IP 패턴 | 일관된 지역 | 빈번한 변경 |
|
|
69
|
+
|
|
70
|
+
### 3단계: reCAPTCHA v3 스코어링
|
|
71
|
+
|
|
72
|
+
```mermaid
|
|
73
|
+
flowchart LR
|
|
74
|
+
A[사용자 접속] --> B{reCAPTCHA v3<br/>행동 분석}
|
|
75
|
+
B -->|Score 0.9| C[정상 사용자]
|
|
76
|
+
B -->|Score 0.5| D[경고 모니터링]
|
|
77
|
+
B -->|Score 0.1| E[봇 의심]
|
|
78
|
+
E --> F[챌린지 또는 차단]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Google 계정 정지 조건
|
|
82
|
+
|
|
83
|
+
> [!WARNING]
|
|
84
|
+
> 다음 행동이 감지되면 계정 정지가 발생할 수 있습니다:
|
|
85
|
+
|
|
86
|
+
1. **비정상적 로그인 패턴**: 동일 계정으로 짧은 시간 내 다수 로그인 시도
|
|
87
|
+
2. **자동화된 로그인**: `navigator.webdriver` 감지
|
|
88
|
+
3. **비정상적 API 요청**: 과도한 요청 빈도
|
|
89
|
+
4. **의심스러운 위치**: 갑작스러운 지역 변경
|
|
90
|
+
5. **서비스 약관 위반**: 자동화 접근 금지 서비스 이용
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 전략 1: 완전 모킹 (권장)
|
|
95
|
+
|
|
96
|
+
> [!TIP]
|
|
97
|
+
> **가장 안전한 접근법**: Google 서버에 전혀 접속하지 않고 테스트
|
|
98
|
+
|
|
99
|
+
### 개념
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
sequenceDiagram
|
|
103
|
+
participant Test as E2E Test
|
|
104
|
+
participant App as NotebookLM App
|
|
105
|
+
participant MSW as MSW Mock
|
|
106
|
+
participant Real as Google 서버
|
|
107
|
+
|
|
108
|
+
Test->>App: 페이지 로드
|
|
109
|
+
App->>MSW: OAuth 요청
|
|
110
|
+
MSW-->>App: 모킹된 응답
|
|
111
|
+
Note right of Real: 실제 서버 접속 없음
|
|
112
|
+
App-->>Test: 테스트 완료
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### MSW (Mock Service Worker) 구현
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// tests/mocks/handlers.ts
|
|
119
|
+
import { http, HttpResponse } from 'msw';
|
|
120
|
+
|
|
121
|
+
export const googleOAuthHandlers = [
|
|
122
|
+
// OAuth 인증 엔드포인트 모킹
|
|
123
|
+
http.get('https://accounts.google.com/o/oauth2/v2/auth', () => {
|
|
124
|
+
const mockAuthCode = 'mock_auth_code_12345';
|
|
125
|
+
return HttpResponse.redirect(
|
|
126
|
+
`http://localhost:3000/callback?code=${mockAuthCode}`
|
|
127
|
+
);
|
|
128
|
+
}),
|
|
129
|
+
|
|
130
|
+
// 토큰 교환 엔드포인트 모킹
|
|
131
|
+
http.post('https://oauth2.googleapis.com/token', () => {
|
|
132
|
+
return HttpResponse.json({
|
|
133
|
+
access_token: 'mock_access_token',
|
|
134
|
+
refresh_token: 'mock_refresh_token',
|
|
135
|
+
expires_in: 3600,
|
|
136
|
+
token_type: 'Bearer',
|
|
137
|
+
});
|
|
138
|
+
}),
|
|
139
|
+
|
|
140
|
+
// 사용자 정보 엔드포인트 모킹
|
|
141
|
+
http.get('https://www.googleapis.com/oauth2/v3/userinfo', () => {
|
|
142
|
+
return HttpResponse.json({
|
|
143
|
+
sub: '123456789',
|
|
144
|
+
name: 'Test User',
|
|
145
|
+
email: 'test@example.com',
|
|
146
|
+
picture: 'https://example.com/avatar.jpg'
|
|
147
|
+
});
|
|
148
|
+
}),
|
|
149
|
+
];
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### NotebookLM API 응답 모킹
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// tests/mocks/notebooklm-handlers.ts
|
|
156
|
+
import { http, HttpResponse } from 'msw';
|
|
157
|
+
|
|
158
|
+
export const notebookLMHandlers = [
|
|
159
|
+
// 채팅 응답 모킹
|
|
160
|
+
http.post('https://notebooklm.google.com/api/chat', async ({ request }) => {
|
|
161
|
+
const body = await request.json();
|
|
162
|
+
|
|
163
|
+
return HttpResponse.json({
|
|
164
|
+
response: `Mocked response for: ${body.question}`,
|
|
165
|
+
sources: [
|
|
166
|
+
{ title: 'Mock Source 1', snippet: 'Relevant content...' }
|
|
167
|
+
],
|
|
168
|
+
thinking_time: 1500,
|
|
169
|
+
});
|
|
170
|
+
}),
|
|
171
|
+
|
|
172
|
+
// 노트북 목록 모킹
|
|
173
|
+
http.get('https://notebooklm.google.com/api/notebooks', () => {
|
|
174
|
+
return HttpResponse.json({
|
|
175
|
+
notebooks: [
|
|
176
|
+
{ id: 'mock-001', title: 'Test Notebook 1' },
|
|
177
|
+
{ id: 'mock-002', title: 'Test Notebook 2' },
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
}),
|
|
181
|
+
];
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Playwright MSW 통합 예제
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// tests/e2e/authenticated-flow.spec.ts
|
|
188
|
+
import { test, expect } from '@playwright/test';
|
|
189
|
+
import { setupMSW } from '../mocks/setup';
|
|
190
|
+
|
|
191
|
+
test.describe('NotebookLM E2E Tests (Mocked)', () => {
|
|
192
|
+
test.beforeEach(async ({ page }) => {
|
|
193
|
+
// MSW 설정
|
|
194
|
+
await setupMSW(page, [
|
|
195
|
+
...googleOAuthHandlers,
|
|
196
|
+
...notebookLMHandlers,
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('should display notebooks after login', async ({ page }) => {
|
|
201
|
+
await page.goto('/');
|
|
202
|
+
|
|
203
|
+
// 로그인 버튼 클릭 (모킹된 OAuth 흐름)
|
|
204
|
+
await page.click('[data-testid="login-button"]');
|
|
205
|
+
|
|
206
|
+
// 노트북 목록 확인
|
|
207
|
+
await expect(page.getByText('Test Notebook 1')).toBeVisible();
|
|
208
|
+
await expect(page.getByText('Test Notebook 2')).toBeVisible();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('should receive chat response', async ({ page }) => {
|
|
212
|
+
await page.goto('/notebook/mock-001');
|
|
213
|
+
|
|
214
|
+
await page.fill('[data-testid="chat-input"]', 'What is AI?');
|
|
215
|
+
await page.click('[data-testid="send-button"]');
|
|
216
|
+
|
|
217
|
+
// 모킹된 응답 확인
|
|
218
|
+
await expect(page.getByText('Mocked response for: What is AI?')).toBeVisible();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 장단점
|
|
224
|
+
|
|
225
|
+
| ✅ 장점 | ❌ 단점 |
|
|
226
|
+
|:---|:---|
|
|
227
|
+
| 계정 위험 0% | 실제 서버 동작과 차이 가능 |
|
|
228
|
+
| 빠른 테스트 실행 | 모킹 유지보수 필요 |
|
|
229
|
+
| CI/CD 친화적 | API 변경 시 업데이트 필요 |
|
|
230
|
+
| 네트워크 독립적 | 모킹 로직 구현 비용 |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 전략 2: 사전인증 세션 재사용
|
|
235
|
+
|
|
236
|
+
> [!IMPORTANT]
|
|
237
|
+
> **1회 수동 로그인 후 세션을 저장**하여 반복 로그인을 회피합니다.
|
|
238
|
+
|
|
239
|
+
### 인증 상태 저장 및 재사용
|
|
240
|
+
|
|
241
|
+
```mermaid
|
|
242
|
+
sequenceDiagram
|
|
243
|
+
participant Manual as 수동 로그인<br/>(1회)
|
|
244
|
+
participant Save as storageState<br/>저장
|
|
245
|
+
participant Test1 as Test 1
|
|
246
|
+
participant Test2 as Test 2
|
|
247
|
+
participant TestN as Test N
|
|
248
|
+
|
|
249
|
+
Manual->>Save: 쿠키/세션 저장
|
|
250
|
+
Save-->>Test1: 상태 로드
|
|
251
|
+
Save-->>Test2: 상태 로드
|
|
252
|
+
Save-->>TestN: 상태 로드
|
|
253
|
+
Note over Test1,TestN: 로그인 없이<br/>인증된 상태로 시작
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 구현: 세션 저장
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// scripts/save-auth-state.ts
|
|
260
|
+
import { chromium } from 'patchright';
|
|
261
|
+
import path from 'path';
|
|
262
|
+
|
|
263
|
+
async function saveAuthenticationState() {
|
|
264
|
+
const browser = await chromium.launch({ headless: false });
|
|
265
|
+
const context = await browser.newContext();
|
|
266
|
+
const page = await context.newPage();
|
|
267
|
+
|
|
268
|
+
// 1. NotebookLM 접속
|
|
269
|
+
await page.goto('https://notebooklm.google.com');
|
|
270
|
+
|
|
271
|
+
// 2. 사용자에게 수동 로그인 요청
|
|
272
|
+
console.log('🔐 Please login manually in the browser window...');
|
|
273
|
+
console.log('⏳ Waiting for you to complete authentication...');
|
|
274
|
+
|
|
275
|
+
// 3. 인증 완료 대기 (특정 URL 또는 요소 감지)
|
|
276
|
+
await page.waitForURL('https://notebooklm.google.com/*', {
|
|
277
|
+
timeout: 300000 // 5분 대기
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// 4. 인증 상태 저장
|
|
281
|
+
const authStatePath = path.join(__dirname, '../.auth/user-state.json');
|
|
282
|
+
await context.storageState({ path: authStatePath });
|
|
283
|
+
|
|
284
|
+
console.log(`✅ Authentication state saved to: ${authStatePath}`);
|
|
285
|
+
console.log('⚠️ Add this file to .gitignore!');
|
|
286
|
+
|
|
287
|
+
await browser.close();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
saveAuthenticationState();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 구현: 세션 로드
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// playwright.config.ts
|
|
297
|
+
import { defineConfig } from '@playwright/test';
|
|
298
|
+
|
|
299
|
+
export default defineConfig({
|
|
300
|
+
projects: [
|
|
301
|
+
// 인증이 필요한 테스트
|
|
302
|
+
{
|
|
303
|
+
name: 'authenticated',
|
|
304
|
+
use: {
|
|
305
|
+
storageState: '.auth/user-state.json',
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
// 비인증 테스트
|
|
309
|
+
{
|
|
310
|
+
name: 'unauthenticated',
|
|
311
|
+
use: {
|
|
312
|
+
storageState: undefined,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// tests/e2e/notebook-operations.spec.ts
|
|
321
|
+
import { test, expect } from '@playwright/test';
|
|
322
|
+
|
|
323
|
+
test.use({ storageState: '.auth/user-state.json' });
|
|
324
|
+
|
|
325
|
+
test.describe('NotebookLM Authenticated Tests', () => {
|
|
326
|
+
test('should access notebooks without login', async ({ page }) => {
|
|
327
|
+
// 이미 인증된 상태로 시작
|
|
328
|
+
await page.goto('https://notebooklm.google.com');
|
|
329
|
+
|
|
330
|
+
// 즉시 노트북 목록 접근 가능
|
|
331
|
+
await expect(page.locator('[data-testid="notebooks-list"]')).toBeVisible();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 세션 만료 처리
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// utils/auth-manager.ts
|
|
340
|
+
import fs from 'fs/promises';
|
|
341
|
+
|
|
342
|
+
export async function isSessionValid(statePath: string): Promise<boolean> {
|
|
343
|
+
try {
|
|
344
|
+
const stateData = await fs.readFile(statePath, 'utf-8');
|
|
345
|
+
const state = JSON.parse(stateData);
|
|
346
|
+
|
|
347
|
+
// 쿠키 만료 확인
|
|
348
|
+
const now = Date.now() / 1000;
|
|
349
|
+
const criticalCookies = ['__Secure-1PSID', '__Secure-3PSID', 'SID'];
|
|
350
|
+
|
|
351
|
+
for (const cookie of state.cookies) {
|
|
352
|
+
if (criticalCookies.includes(cookie.name)) {
|
|
353
|
+
if (cookie.expires && cookie.expires < now) {
|
|
354
|
+
console.warn(`Cookie ${cookie.name} has expired`);
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return true;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Failed to validate session:', error);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### 보안 주의사항
|
|
369
|
+
|
|
370
|
+
> [!CAUTION]
|
|
371
|
+
> 세션 파일은 민감한 정보를 포함합니다!
|
|
372
|
+
|
|
373
|
+
```gitignore
|
|
374
|
+
# .gitignore
|
|
375
|
+
.auth/
|
|
376
|
+
*.auth.json
|
|
377
|
+
user-state.json
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
# CI/CD 환경 변수로 관리
|
|
382
|
+
# GitHub Actions 예제
|
|
383
|
+
env:
|
|
384
|
+
AUTH_STATE: ${{ secrets.NOTEBOOK_AUTH_STATE }}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 전략 3: Patchright 스텔스 모드
|
|
390
|
+
|
|
391
|
+
### Patchright란?
|
|
392
|
+
|
|
393
|
+
Patchright는 Playwright의 "언디텍티드" 버전으로, 브라우저 자동화 감지를 회피하도록 설계되었습니다.
|
|
394
|
+
|
|
395
|
+
> [!NOTE]
|
|
396
|
+
> 현재 프로젝트는 이미 Patchright 1.56.0을 사용하고 있습니다.
|
|
397
|
+
|
|
398
|
+
### Patchright의 감지 회피 기법
|
|
399
|
+
|
|
400
|
+
```mermaid
|
|
401
|
+
graph TD
|
|
402
|
+
subgraph "Playwright (감지됨)"
|
|
403
|
+
A1[Runtime.enable 호출] --> B1[navigator.webdriver = true]
|
|
404
|
+
B1 --> C1[HeadlessChrome UA]
|
|
405
|
+
C1 --> D1[--enable-automation 플래그]
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
subgraph "Patchright (스텔스)"
|
|
409
|
+
A2[Isolated Context 실행] --> B2[navigator.webdriver = undefined]
|
|
410
|
+
B2 --> C2[Chrome UA]
|
|
411
|
+
C2 --> D2[자동화 플래그 제거]
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
style A1 fill:#ff6b6b
|
|
415
|
+
style B1 fill:#ff6b6b
|
|
416
|
+
style C1 fill:#ff6b6b
|
|
417
|
+
style D1 fill:#ff6b6b
|
|
418
|
+
style A2 fill:#51cf66
|
|
419
|
+
style B2 fill:#51cf66
|
|
420
|
+
style C2 fill:#51cf66
|
|
421
|
+
style D2 fill:#51cf66
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### 주요 패치 내용
|
|
425
|
+
|
|
426
|
+
| 감지 포인트 | Playwright | Patchright |
|
|
427
|
+
|:---|:---|:---|
|
|
428
|
+
| `navigator.webdriver` | `true` | `undefined` |
|
|
429
|
+
| User-Agent | `HeadlessChrome` | `Chrome` |
|
|
430
|
+
| `Runtime.enable` | 호출됨 | 격리된 컨텍스트 사용 |
|
|
431
|
+
| `Console.enable` | 호출됨 | 패치됨 |
|
|
432
|
+
| `--enable-automation` | 있음 | 제거됨 |
|
|
433
|
+
| 팝업 차단 | 비활성화 | 기본값 유지 |
|
|
434
|
+
|
|
435
|
+
### 현재 프로젝트의 스텔스 구현
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// src/utils/stealth-utils.ts (현재 구현)
|
|
439
|
+
export async function humanType(
|
|
440
|
+
page: Page,
|
|
441
|
+
selector: string,
|
|
442
|
+
text: string,
|
|
443
|
+
options?: { wpm?: number; typos?: boolean }
|
|
444
|
+
): Promise<void> {
|
|
445
|
+
if (!CONFIG.stealthEnabled || !CONFIG.stealthHumanTyping) {
|
|
446
|
+
await page.fill(selector, text);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 인간적인 타이핑 시뮬레이션
|
|
451
|
+
const wpm = options?.wpm ?? randomInt(CONFIG.typingMinWPM, CONFIG.typingMaxWPM);
|
|
452
|
+
const baseDelay = (60 * 1000) / (wpm * 5);
|
|
453
|
+
|
|
454
|
+
for (let i = 0; i < text.length; i++) {
|
|
455
|
+
const char = text[i];
|
|
456
|
+
|
|
457
|
+
// 오타 시뮬레이션 (5% 확률)
|
|
458
|
+
if (options?.typos && Math.random() < 0.05 && i > 0) {
|
|
459
|
+
const typoChar = randomChar();
|
|
460
|
+
await page.fill(selector, currentText + typoChar);
|
|
461
|
+
await randomDelay(100, 200);
|
|
462
|
+
// 백스페이스
|
|
463
|
+
currentText = currentText.slice(0, -1);
|
|
464
|
+
await page.fill(selector, currentText);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
currentText += char;
|
|
468
|
+
await page.fill(selector, currentText);
|
|
469
|
+
|
|
470
|
+
// 문자별 지연
|
|
471
|
+
let delay = baseDelay * randomFloat(0.8, 1.2);
|
|
472
|
+
if (char === ' ' || char === '.' || char === '!') {
|
|
473
|
+
delay *= 1.5; // 문장부호 후 더 긴 지연
|
|
474
|
+
}
|
|
475
|
+
await sleep(delay);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// src/utils/stealth-utils.ts
|
|
482
|
+
export async function randomMouseMovement(
|
|
483
|
+
page: Page,
|
|
484
|
+
endX?: number,
|
|
485
|
+
endY?: number,
|
|
486
|
+
steps?: number
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
if (!CONFIG.stealthEnabled || !CONFIG.stealthMouseMovements) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 베지어 커브 기반 마우스 이동
|
|
493
|
+
for (let step = 0; step < steps; step++) {
|
|
494
|
+
const progress = step / steps;
|
|
495
|
+
|
|
496
|
+
// 자연스러운 곡선 이동
|
|
497
|
+
const jitterX = Math.sin(progress * Math.PI) * randomInt(-10, 10);
|
|
498
|
+
const jitterY = Math.cos(progress * Math.PI) * randomInt(-10, 10);
|
|
499
|
+
|
|
500
|
+
const currentX = startX + (endX - startX) * progress + jitterX;
|
|
501
|
+
const currentY = startY + (endY - startY) * progress + jitterY;
|
|
502
|
+
|
|
503
|
+
await page.mouse.move(currentX, currentY);
|
|
504
|
+
|
|
505
|
+
// 이동 속도 변화 (가속→감속)
|
|
506
|
+
const delay = 10 + 30 * Math.sin(2.5 * progress);
|
|
507
|
+
await sleep(delay);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### 고급 스텔스 설정
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
// patchright-config.ts
|
|
516
|
+
import { chromium } from 'patchright';
|
|
517
|
+
|
|
518
|
+
export async function launchStealthBrowser() {
|
|
519
|
+
const browser = await chromium.launch({
|
|
520
|
+
headless: false, // 헤드리스 감지 회피
|
|
521
|
+
channel: 'chrome', // 실제 Chrome 사용
|
|
522
|
+
|
|
523
|
+
args: [
|
|
524
|
+
'--disable-blink-features=AutomationControlled',
|
|
525
|
+
'--no-sandbox',
|
|
526
|
+
'--disable-setuid-sandbox',
|
|
527
|
+
'--disable-dev-shm-usage',
|
|
528
|
+
'--disable-accelerated-2d-canvas',
|
|
529
|
+
'--no-first-run',
|
|
530
|
+
'--no-zygote',
|
|
531
|
+
'--disable-gpu',
|
|
532
|
+
'--hide-scrollbars',
|
|
533
|
+
'--mute-audio',
|
|
534
|
+
],
|
|
535
|
+
|
|
536
|
+
ignoreDefaultArgs: ['--enable-automation'],
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const context = await browser.newContext({
|
|
540
|
+
// 실제 사용자 User-Agent
|
|
541
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
|
|
542
|
+
'AppleWebKit/537.36 (KHTML, like Gecko) ' +
|
|
543
|
+
'Chrome/120.0.0.0 Safari/537.36',
|
|
544
|
+
|
|
545
|
+
// 실제 뷰포트 크기
|
|
546
|
+
viewport: { width: 1920, height: 1080 },
|
|
547
|
+
|
|
548
|
+
// 지역 설정
|
|
549
|
+
locale: 'ko-KR',
|
|
550
|
+
timezoneId: 'Asia/Seoul',
|
|
551
|
+
|
|
552
|
+
// 권한 설정
|
|
553
|
+
permissions: ['geolocation', 'notifications'],
|
|
554
|
+
|
|
555
|
+
// 지리적 위치
|
|
556
|
+
geolocation: { latitude: 37.5665, longitude: 126.9780 }, // 서울
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
return { browser, context };
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### 여전히 존재하는 위험
|
|
564
|
+
|
|
565
|
+
> [!WARNING]
|
|
566
|
+
> Patchright를 사용해도 **Google 로그인 자동화는 위험**합니다!
|
|
567
|
+
|
|
568
|
+
- Google은 지속적으로 감지 로직을 업데이트
|
|
569
|
+
- 오픈소스 스텔스 도구는 뒤처질 수 있음
|
|
570
|
+
- 계정 정지는 복구가 어려움
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## 전략 4: Google API 대안 탐색
|
|
575
|
+
|
|
576
|
+
### NotebookLM Enterprise API
|
|
577
|
+
|
|
578
|
+
Google은 기업 사용자를 위한 NotebookLM Enterprise API를 제공합니다.
|
|
579
|
+
|
|
580
|
+
```http
|
|
581
|
+
# NotebookLM Enterprise API Endpoints
|
|
582
|
+
POST /v1/notebooks
|
|
583
|
+
GET /v1/notebooks/{notebookId}
|
|
584
|
+
POST /v1/notebooks/{notebookId}/sources
|
|
585
|
+
POST /v1/notebooks/{notebookId}/chat
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
> [!NOTE]
|
|
589
|
+
> Enterprise API 접근은 Google Cloud 계정과 별도 계약이 필요합니다.
|
|
590
|
+
|
|
591
|
+
### 오픈소스 대안: Open Notebook
|
|
592
|
+
|
|
593
|
+
[Open Notebook](https://github.com/smol-ai/open-notebook)은 NotebookLM의 오픈소스 대안으로, 자체 호스팅이 가능합니다.
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
# Docker로 Open Notebook 실행
|
|
597
|
+
docker run -d \
|
|
598
|
+
-p 3000:3000 \
|
|
599
|
+
-e LLM_PROVIDER=openai \
|
|
600
|
+
-e OPENAI_API_KEY=sk-xxx \
|
|
601
|
+
opennotebook/server:latest
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Vertex AI 대안
|
|
605
|
+
|
|
606
|
+
Google Cloud Vertex AI를 사용하면 공식 API로 유사한 기능을 구현할 수 있습니다.
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
// Vertex AI 문서 요약 예제
|
|
610
|
+
import { VertexAI } from '@google-cloud/vertexai';
|
|
611
|
+
|
|
612
|
+
const vertexAI = new VertexAI({
|
|
613
|
+
project: 'your-project',
|
|
614
|
+
location: 'us-central1',
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const model = vertexAI.getGenerativeModel({
|
|
618
|
+
model: 'gemini-1.5-pro',
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
async function summarizeDocument(content: string) {
|
|
622
|
+
const result = await model.generateContent({
|
|
623
|
+
contents: [{ role: 'user', parts: [{ text: content }] }],
|
|
624
|
+
systemInstruction: 'You are a document summarization assistant.',
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
return result.response.candidates[0].content;
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## 권장 테스트 아키텍처
|
|
634
|
+
|
|
635
|
+
### 테스트 피라미드 적용
|
|
636
|
+
|
|
637
|
+
```mermaid
|
|
638
|
+
graph TB
|
|
639
|
+
subgraph "E2E Tests (실제 브라우저)"
|
|
640
|
+
E1[세션 재사용 테스트]
|
|
641
|
+
E2[Patchright 스텔스]
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
subgraph "Integration Tests (가장 권장)"
|
|
645
|
+
I1[MSW 모킹 테스트]
|
|
646
|
+
I2[API 통합 테스트]
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
subgraph "Unit Tests"
|
|
650
|
+
U1[유틸리티 테스트]
|
|
651
|
+
U2[컴포넌트 테스트]
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
E1 --> I1
|
|
655
|
+
E2 --> I1
|
|
656
|
+
I1 --> U1
|
|
657
|
+
I2 --> U2
|
|
658
|
+
|
|
659
|
+
style I1 fill:#40c057
|
|
660
|
+
style I2 fill:#40c057
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### 실제 테스트 전략 구성
|
|
664
|
+
|
|
665
|
+
```typescript
|
|
666
|
+
// vitest.config.ts
|
|
667
|
+
import { defineConfig } from 'vitest/config';
|
|
668
|
+
|
|
669
|
+
export default defineConfig({
|
|
670
|
+
test: {
|
|
671
|
+
// 단위 테스트: 빠르고 안전
|
|
672
|
+
include: ['src/**/*.test.ts'],
|
|
673
|
+
exclude: ['tests/e2e/**'],
|
|
674
|
+
|
|
675
|
+
// 커버리지
|
|
676
|
+
coverage: {
|
|
677
|
+
reporter: ['text', 'json', 'html'],
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
});
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
// playwright.config.ts
|
|
685
|
+
import { defineConfig } from '@playwright/test';
|
|
686
|
+
|
|
687
|
+
export default defineConfig({
|
|
688
|
+
projects: [
|
|
689
|
+
// 1. 모킹 기반 테스트 (권장)
|
|
690
|
+
{
|
|
691
|
+
name: 'mocked',
|
|
692
|
+
testDir: './tests/mocked',
|
|
693
|
+
use: {
|
|
694
|
+
baseURL: 'http://localhost:3000',
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
|
|
698
|
+
// 2. 세션 재사용 테스트 (수동 로그인 필요)
|
|
699
|
+
{
|
|
700
|
+
name: 'authenticated',
|
|
701
|
+
testDir: './tests/authenticated',
|
|
702
|
+
use: {
|
|
703
|
+
storageState: '.auth/user-state.json',
|
|
704
|
+
baseURL: 'https://notebooklm.google.com',
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
|
|
709
|
+
// 병렬 실행 제한 (실제 서버 테스트)
|
|
710
|
+
workers: process.env.CI ? 1 : undefined,
|
|
711
|
+
|
|
712
|
+
// 재시도 설정
|
|
713
|
+
retries: process.env.CI ? 2 : 0,
|
|
714
|
+
});
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## 결론 및 권장사항
|
|
720
|
+
|
|
721
|
+
### 최종 권장 전략
|
|
722
|
+
|
|
723
|
+
1. **개발 단계**: MSW 모킹 사용 (100% 안전)
|
|
724
|
+
2. **로컬 검증**: 세션 재사용 + Patchright 스텔스
|
|
725
|
+
3. **CI/CD**: 모킹 테스트만 실행
|
|
726
|
+
4. **프로덕션 검증**: 수동 테스트 또는 Enterprise API
|
|
727
|
+
|
|
728
|
+
### 체크리스트
|
|
729
|
+
|
|
730
|
+
- [ ] MSW 핸들러 구현
|
|
731
|
+
- [ ] Playwright storageState 설정
|
|
732
|
+
- [ ] .gitignore에 인증 파일 추가
|
|
733
|
+
- [ ] CI/CD 파이프라인에 모킹 테스트 통합
|
|
734
|
+
- [ ] 세션 만료 처리 로직 구현
|
|
735
|
+
- [ ] 테스트 전용 Google 계정 생성 (선택)
|
|
736
|
+
|
|
737
|
+
### 절대 하지 말아야 할 것
|
|
738
|
+
|
|
739
|
+
> [!CAUTION]
|
|
740
|
+
>
|
|
741
|
+
> 1. ❌ 실제 Google 계정으로 자동화 로그인 반복 시도
|
|
742
|
+
> 2. ❌ 인증 파일을 버전 관리에 포함
|
|
743
|
+
> 3. ❌ CI/CD에서 실제 Google 로그인 실행
|
|
744
|
+
> 4. ❌ 프록시 우회로 다중 세션 생성 시도
|
|
745
|
+
|
|
746
|
+
---
|
|
747
|
+
|
|
748
|
+
## 참고 자료
|
|
749
|
+
|
|
750
|
+
- [Patchright GitHub](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-nodejs)
|
|
751
|
+
- [MSW Documentation](https://mswjs.io/)
|
|
752
|
+
- [Playwright Authentication](https://playwright.dev/docs/auth)
|
|
753
|
+
- [Google Cloud Vertex AI](https://cloud.google.com/vertex-ai)
|
|
754
|
+
- [Open Notebook (오픈소스 대안)](https://github.com/smol-ai/open-notebook)
|