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 = `
-
- `;
-
- 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 = `
-
- `;
- 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 = `
-
- `;
- 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");
}
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;
}
}