From 427e3f9705b76c8c497259725ad6861e537aea7d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 10 Feb 2022 03:13:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?`ElementParser`=20=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=EC=97=86=EB=8A=94=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/naver/elementParser.test.ts | 156 +--------------------------- src/app/naver/elementParser.ts | 109 ------------------- 2 files changed, 3 insertions(+), 262 deletions(-) diff --git a/src/app/naver/elementParser.test.ts b/src/app/naver/elementParser.test.ts index 80bba02..40d7a10 100644 --- a/src/app/naver/elementParser.test.ts +++ b/src/app/naver/elementParser.test.ts @@ -1,157 +1,7 @@ import PaymentElementParser from "./elementParser"; -function getWrappedHtml(htmlString: string) { - return `
${htmlString}
`; -} - -describe("Parse payment history", () => { - let paymentElementParser: PaymentElementParser; - beforeEach(() => { - paymentElementParser = new PaymentElementParser(page); - }); - it("Should not return 'purchasedAt' but with 'isAdditional' to be truthy if the product is '추가상품' ", async () => { - // given - const status = "결제완료"; - const productName = "쿠키 1개"; - const priceString = "100원"; - const imageURL = "https://example/example.png"; - const HTML = ` -
-
${status}
- -
- `; - - const wrappedHTML = getWrappedHtml(HTML); - await page.setContent(wrappedHTML); - - const element = await page.$("div"); - - if (element === null) { - throw new Error("element is null"); - } - - // when - const paymentHistory = await paymentElementParser.parseElement(element); - - // then - expect(paymentHistory.name).toEqual(productName); - expect(paymentHistory.price).toEqual(100); - expect(paymentHistory.thumbnailURL).toEqual(imageURL); - expect(paymentHistory.paymentStatus).toEqual(status); - expect(paymentHistory.isAdditional).toBeTruthy(); - expect(paymentHistory.purchasedAt).toBeUndefined(); - }); - describe("Should return PaymentHistory", () => { - it("When the date is current year", async () => { - // given - const status = "구매확정완료"; - const productName = - "[병행]로지텍 K380 무선 블루투스 키보드(국내당일출고)"; - const priceString = "31,500원"; - const imageURL = "https://example/example.png"; - const dateString = "12. 17. 결제"; - const HTML = ` -
-
${status}
- -
- `; - const wrappedHTML = getWrappedHtml(HTML); - await page.setContent(wrappedHTML); - const element = await page.$("div"); - if (element === null) { - throw new Error("element is null"); - } - - // when - const paymentHistory = await paymentElementParser.parseElement(element); - - // then - expect(paymentHistory.name).toEqual(productName); - expect(paymentHistory.price).toEqual(31500); - expect(paymentHistory.thumbnailURL).toEqual(imageURL); - expect(paymentHistory.paymentStatus).toEqual(status); - expect(paymentHistory.isAdditional).toBeFalsy(); - expect(paymentHistory.purchasedAt).not.toBeUndefined(); - - const date = paymentHistory.purchasedAt; - expect(date?.getFullYear()).toEqual(new Date().getFullYear()); - expect(date?.getMonth()).toEqual(11); - expect(date?.getDate()).toEqual(17); - }); - it("When the date is not current year", async () => { - // given - const status = "구매확정완료"; - const productName = "바세린 퓨어 스킨젤리 100ml x 3개"; - const priceString = "7,900원"; - const imageURL = "https://example/example.png"; - const dateString = "2020. 11. 27. 결제"; - const HTML = ` -
-
${status}
- -
- `; - const wrappedHTML = getWrappedHtml(HTML); - await page.setContent(wrappedHTML); - const element = await page.$("div"); - if (element === null) { - throw new Error("element is null"); - } - - // when - const paymentHistory = await paymentElementParser.parseElement(element); - - // then - expect(paymentHistory.name).toEqual(productName); - expect(paymentHistory.price).toEqual(7900); - expect(paymentHistory.thumbnailURL).toEqual(imageURL); - expect(paymentHistory.paymentStatus).toEqual(status); - expect(paymentHistory.isAdditional).toBeFalsy(); - expect(paymentHistory.purchasedAt).not.toBeUndefined(); - - const date = paymentHistory.purchasedAt; - expect(date?.getFullYear()).toEqual(2020); - expect(date?.getMonth()).toEqual(10); - expect(date?.getDate()).toEqual(27); - }); +describe("ElementParser", () => { + it("should be defined", () => { + expect(PaymentElementParser).toBeDefined(); }); }); diff --git a/src/app/naver/elementParser.ts b/src/app/naver/elementParser.ts index 11ca512..765aed5 100644 --- a/src/app/naver/elementParser.ts +++ b/src/app/naver/elementParser.ts @@ -1,119 +1,10 @@ import puppeteer from "puppeteer"; -import { PaymentHistory } from "../common"; export default class ElementParser { - private static async parseName(element: puppeteer.ElementHandle) { - const name = - (await element.$eval( - 'strong[class^="PaymentList_name__"]', - (el) => el.textContent - )) || ""; - return name; - } - constructor(private readonly page: puppeteer.Page) { this.page = page; } - private static async parsePrice(element: puppeteer.ElementHandle) { - const priceString = - (await element.$eval( - 'div[class^="PaymentList_sum__"]', - (el) => el.textContent - )) || "0"; - - const price = +priceString.replace(/[^0-9]/g, ""); - - return price; - } - - private static async parseThumbnailUrl(element: puppeteer.ElementHandle) { - const thumbnailURL = await element.$eval( - 'div[class^="PaymentList_thumb__"] > img', - (el) => el.getAttribute("src") - ); - return thumbnailURL !== null ? thumbnailURL : ""; - } - - private static async parsePaymentStatus(element: puppeteer.ElementHandle) { - const paymentStatusString = - (await element.$eval( - 'div[class^="PaymentList_status__"]', - (el) => el.textContent - )) || ""; - - return paymentStatusString; - } - - private static async parsePurchasedAtString( - element: puppeteer.ElementHandle - ) { - const purchasedAtDateString = - (await element.$eval( - "div[class^='PaymentList_date__']", - (el) => el.textContent - )) || ""; - - return purchasedAtDateString; - } - - private static parsePurchasedAt(purchasedAtDateString: string) { - let splittedByDot = purchasedAtDateString.split("."); - if (splittedByDot.length === 1) { - return; - } - splittedByDot = splittedByDot.slice(0, splittedByDot.length - 1); - - const isThisYear = splittedByDot.length == 2; - - if (isThisYear) { - const year = new Date().getFullYear(); - const month = +splittedByDot[0].trim(); - const day = +splittedByDot[1].trim(); - return new Date(year, month - 1, day); - } - - const year = +splittedByDot[0].trim(); - const month = +splittedByDot[1].trim(); - const day = +splittedByDot[2].trim(); - return new Date(year, month - 1, day); - } - - private static parseIsAdditional(purchasedAtDateString: string) { - return purchasedAtDateString.trim() === "추가상품"; - } - - async parsePaymentElements() { - const elements = await this.page.$$( - "div[class^='PaymentList_article__'] > ul > li" - ); - return elements; - } - - async parseElement(element: puppeteer.ElementHandle) { - const [name, price, thumbnailURL, paymentStatus, purchasedAtDateString] = - await Promise.all([ - ElementParser.parseName(element), - ElementParser.parsePrice(element), - ElementParser.parseThumbnailUrl(element), - ElementParser.parsePaymentStatus(element), - ElementParser.parsePurchasedAtString(element), - ]); - - const purchasedAt = ElementParser.parsePurchasedAt(purchasedAtDateString); - const isAdditional = ElementParser.parseIsAdditional(purchasedAtDateString); - - const paymentHistory: PaymentHistory = { - name, - price, - thumbnailURL, - paymentStatus, - purchasedAt, - isAdditional, - }; - return paymentHistory; - } - async parseManualOTPInputElement() { return await this.page.$("#otp"); } From 805efdc6d481c2b8c075d58b261ea82c868a1bb3 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Thu, 10 Feb 2022 03:21:05 +0900 Subject: [PATCH 2/2] =?UTF-8?q?`paymentHistory`=20=EC=99=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=ED=95=9C=20`pageInteractor`=20=EC=9D=98=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/naver/pageInteractor.ts | 114 +++++++++++--------------------- 1 file changed, 39 insertions(+), 75 deletions(-) diff --git a/src/app/naver/pageInteractor.ts b/src/app/naver/pageInteractor.ts index 551c427..552c6ba 100644 --- a/src/app/naver/pageInteractor.ts +++ b/src/app/naver/pageInteractor.ts @@ -29,7 +29,6 @@ export default class PageInteractor { this.page.waitForNavigation({ waitUntil: "networkidle2" }), ]); } - private async typeLoginInfo(id: string, password: string, delay: number) { await this.page.focus("#id"); await this.page.keyboard.type(id, { delay: delay }); @@ -37,28 +36,18 @@ export default class PageInteractor { await this.page.keyboard.type(password, { delay: delay }); await this.clickLoginButton(); } - private async waitForLoginElements() { await Promise.all([ this.page.waitForSelector("#id"), this.page.waitForSelector("#pw"), ]); } - async login(id: string, password: string, delay?: number, loginURL?: string) { await this.waitForLoginElements(); await this.typeLoginInfo(id, password, delay || 200); } - async getCookies() { - const cookies = await this.page.cookies(); - let cookieString = ""; - for (const cookie of cookies) { - cookieString += `${cookie.name}=${cookie.value}; `; - } - return cookieString; - } - + otpInputSelector = "#otp"; async getLoginStatus(loginURL?: string): Promise { const isLoginPage = this.page .url() @@ -78,8 +67,7 @@ export default class PageInteractor { return "otp-required"; } - const manualOTPElement = - await this.elementParser.parseManualOTPInputElement(); + const manualOTPElement = await this.page.$(this.otpInputSelector); if (manualOTPElement) { return "manual-otp-required"; } @@ -87,9 +75,36 @@ export default class PageInteractor { return "unexpected"; } + captchaImageSelector = "#captchaimg"; + captchaTextSelector = "#captcha_info"; + async getCaptchaStatus(): Promise { + const data = await this.page.evaluate( + (captchaImageSelector: string, captchaTextSelector: string) => { + const captchaImage = document.querySelector( + captchaImageSelector + ) as HTMLElement | null; + const captchaText = document.querySelector( + captchaTextSelector + ) as HTMLElement | null; + + if (!captchaImage || !captchaText) { + return; + } + + const imageData = captchaImage.getAttribute("src") as string; + const question = captchaText.innerText; + + return { imageData, question }; + }, + this.captchaImageSelector, + this.captchaTextSelector + ); + + return data || null; + } + async fillManualOTPInput(code: string) { - const manualOTPElement = - await this.elementParser.parseManualOTPInputElement(); + const manualOTPElement = await this.page.$(this.otpInputSelector); if (!manualOTPElement) { throw new Error("manual-otp-input-element not found"); } @@ -97,30 +112,9 @@ export default class PageInteractor { await manualOTPElement.press("Enter"); } - async getCaptchaStatus(): Promise { - const data = await this.page.evaluate(() => { - const captchaImage = document.querySelector( - "#captchaimg" - ) as HTMLElement | null; - const captchaText = document.querySelector( - "#captcha_info" - ) as HTMLElement | null; - - if (!captchaImage || !captchaText) { - return; - } - - const imageData = captchaImage.getAttribute("src") as string; - const question = captchaText.innerText; - - return { imageData, question }; - }); - - return data || null; - } - + catchaInputSelector = "#captcha"; async fillCaptchaInput(answer: string, password: string) { - const captchaElement = await this.elementParser.parseCaptchaInputElement(); + const captchaElement = await this.page.$(this.catchaInputSelector); if (!captchaElement) { throw new Error("captcha input element not found"); } @@ -129,42 +123,12 @@ export default class PageInteractor { await this.typeLoginInfo("", password, 200); } - async loadMoreHistory() { - if (this._fullyLoaded) { - return; - } - - const buttonSelector = "div[class^='ButtonPrevList_article__'] > button"; - const currentScrollHeight: number = await this.page.evaluate(() => { - return document.body.scrollHeight; - }); - const loadMoreButton = await this.page.$(buttonSelector); - - await loadMoreButton?.click(); - try { - await this.page.waitForFunction( - // wait 1000ms to scroll height increases - (currentScrollHeight: number) => { - return document.body.scrollHeight > currentScrollHeight; - }, - { timeout: 2000 }, - currentScrollHeight - ); - } catch (e) { - if (e instanceof puppeteer.errors.TimeoutError) { - const newLoadMoreButton = await this.page.$(buttonSelector); - if (newLoadMoreButton === null) { - this._fullyLoaded = true; - } - return; - } - console.error(e); - } - } - - async loadPaymentHistoryUntilPageEnds() { - while (!this._fullyLoaded) { - await this.loadMoreHistory(); + async getCookies() { + const cookies = await this.page.cookies(); + let cookieString = ""; + for (const cookie of cookies) { + cookieString += `${cookie.name}=${cookie.value}; `; } + return cookieString; } }