From 10796e865e388a35b7577a10f50fd3c2c3c8c6bf Mon Sep 17 00:00:00 2001 From: jonghyeokFF Date: Wed, 6 May 2026 23:34:18 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(home-inquiry):=20=ED=99=88=201:1?= =?UTF-8?q?=EB=AC=B8=EC=9D=98=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 홈 배너 진입, 지점명/서비스영역 선택, 주차 라디오, 제목/내용 입력, 등록 및 확인 처리 구현 Co-Authored-By: Claude Sonnet 4.6 --- fastfive-auto-app-dev/pages/__init__.py | 5 + .../pages/home_inquiry_page.py | 209 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 fastfive-auto-app-dev/pages/home_inquiry_page.py diff --git a/fastfive-auto-app-dev/pages/__init__.py b/fastfive-auto-app-dev/pages/__init__.py index fa054a8..0c02f98 100644 --- a/fastfive-auto-app-dev/pages/__init__.py +++ b/fastfive-auto-app-dev/pages/__init__.py @@ -1,3 +1,8 @@ from .base_page import BasePage from .login_page import LoginPage from .reservation_page import ReservationPage +from .entrance_card_page import EntranceCardPage +from .entrance_card_issue_page import EntranceCardIssuePage +from .lounge_page import LoungePage +from .my_account_page import MyAccountPage +from .home_inquiry_page import HomeInquiryPage diff --git a/fastfive-auto-app-dev/pages/home_inquiry_page.py b/fastfive-auto-app-dev/pages/home_inquiry_page.py new file mode 100644 index 0000000..d11c330 --- /dev/null +++ b/fastfive-auto-app-dev/pages/home_inquiry_page.py @@ -0,0 +1,209 @@ +from selenium.webdriver.common.by import By +from .base_page import BasePage + + +class HomeInquiryPage(BasePage): + ANDROID = { + 'home_tab': (By.XPATH, '//*[@content-desc="홈" or @text="홈"]'), + 'inquiry_banner': (By.XPATH, '//*[contains(@text, "1:1문의하기") or contains(@text, "궁금한 점")]'), + 'branch_name_btn': (By.XPATH, '//*[@text="지점명을 선택해 주세요."]'), + 'service_area_btn':(By.XPATH, '//*[@text="서비스 영역을 선택해 주세요."]'), + 'parking_radio': (By.XPATH, '//*[contains(@text, "주차")]'), + 'title_input': (By.XPATH, '//*[contains(@hint, "제목을 입력")]'), + 'content_input': (By.XPATH, '//*[contains(@hint, "문의하실 내용")]'), + 'submit_btn': (By.XPATH, '//*[@text="등록하기"]'), + 'confirm_btn': (By.XPATH, '//*[@text="확인"]'), + } + + IOS = { + 'home_tab': (By.XPATH, '//XCUIElementTypeButton[contains(@label, "홈")]'), + 'inquiry_banner': (By.XPATH, '//*[contains(@name, "1:1문의하기") or contains(@name, "궁금한 점")]'), + 'branch_name_btn': (By.XPATH, '//XCUIElementTypeOther[@name="지점명"]'), + 'service_area_btn':(By.XPATH, '//XCUIElementTypeOther[@name="서비스 영역"]'), + 'parking_radio': (By.XPATH, '//*[contains(@name, "주차")]'), + 'title_input': (By.XPATH, '//XCUIElementTypeTextField | //XCUIElementTypeTextView'), + 'content_input': (By.XPATH, '//XCUIElementTypeTextView'), + 'submit_btn': (By.XPATH, '//XCUIElementTypeOther[@name="등록하기"]'), + 'confirm_btn': (By.XPATH, '//XCUIElementTypeButton[@name="확인"] | //XCUIElementTypeOther[@name="확인"]'), + } + + def get_selector(self, key): + return self.ANDROID[key] if self.is_android() else self.IOS[key] + + def go_to_home_tab(self): + self.click(self.get_selector('home_tab')) + print("홈 탭 클릭") + self.wait_seconds(2) + + def click_inquiry_banner(self): + if self.is_ios(): + els = self.driver.find_elements(By.XPATH, '//*[contains(@name, "궁금한 점")]') + if els: + el = els[0] + cx = el.location['x'] + el.size['width'] // 2 + cy = el.location['y'] + el.size['height'] // 2 + print(f"배너 tap ({cx}, {cy})") + self.driver.execute_script('mobile: tap', {'x': cx, 'y': cy}) + else: + w = self.driver.get_window_size() + self.driver.execute_script('mobile: tap', {'x': w['width'] // 2, 'y': int(w['height'] * 0.66)}) + else: + self.click(self.get_selector('inquiry_banner')) + print("1:1문의 배너 클릭") + self.wait_seconds(2) + if self.is_android(): + with open("ps_android_1_form.xml", "w", encoding="utf-8") as f: + f.write(self.driver.page_source) + + def click_branch_name(self): + if self.is_ios(): + els = self.driver.find_elements(By.XPATH, '//*[@name="지점명"]') + btn = next((el for el in els if el.size['width'] > 100), None) + if btn: + cx = btn.location['x'] + btn.size['width'] // 2 + cy = btn.location['y'] + btn.size['height'] // 2 + print(f"지점명 드롭다운 tap ({cx}, {cy})") + self.driver.execute_script('mobile: tap', {'x': cx, 'y': cy}) + else: + self.driver.execute_script('mobile: tap', {'x': 201, 'y': 569}) + else: + self.click(self.get_selector('branch_name_btn')) + print("지점명 클릭") + self.wait_seconds(2) + if self.is_android(): + with open("ps_android_2_branch_picker.xml", "w", encoding="utf-8") as f: + f.write(self.driver.page_source) + + def select_first_branch(self): + if self.is_ios(): + candidates = self.driver.find_elements(By.XPATH, '//*[@accessible="true"]') + exclude = { + '지점명', '서비스 영역', '문의 내용 ', '등록하기', '파일 업로드', '회사명', + '이메일 주소', '휴대폰 번호', '1:1 문의하기', '문의 등록', '문의 내역', + '홈', '예약', '출입카드', '라운지', '내 계정', + } + targets = [el for el in candidates + if el.get_attribute('name') + and not any(ex in (el.get_attribute('name') or '') for ex in exclude) + and el.size['width'] > 50 and el.size['height'] > 20] + print(f"지점명 피커 후보: {[el.get_attribute('name') for el in targets[:5]]}") + if targets: + targets[0].click() + print(f"지점명 선택: {targets[0].get_attribute('name')}") + self.wait_seconds(1) + else: + items = self.driver.find_elements(By.XPATH, '//android.widget.TextView') + exclude = { + '지점명', '서비스 영역', '등록하기', '', '1:1 문의하기', '1:1문의하기', + '문의 등록', '문의 내역', '홈', '예약', '출입카드', '라운지', '내 계정', + '상담시간 : 월-금 9:30-17:30', '문의 내용', '문의 내용 ', '파일 업로드', + '회사명', '이메일 주소', '휴대폰 번호', '0/20', + '지점명을 선택해 주세요.', '서비스 영역을 선택해 주세요.', + '제목을 입력해 주세요. (최대 20자)', '문의하실 내용을 입력해 주세요.', + } + exclude_contains = [ + '휴무시간', '긴급상황', '제휴 문의', '파일은 최대', '이미지, 동영상', + '문의 등록 후에는', '상담시간', + ] + targets = [ + el for el in items + if el.text not in exclude + and el.text.strip() + and not any(ex in el.text for ex in exclude_contains) + ] + print(f"지점명 피커 후보(Android): {[el.text for el in targets[:5]]}") + if targets: + targets[0].click() + print(f"지점명 선택: {targets[0].text}") + self.wait_seconds(1) + + def click_service_area(self): + if self.is_ios(): + els = self.driver.find_elements(By.XPATH, '//*[@name="서비스 영역"]') + btn = next((el for el in els if el.size['width'] > 100), None) + if btn: + cx = btn.location['x'] + btn.size['width'] // 2 + cy = btn.location['y'] + btn.size['height'] // 2 + print(f"서비스 영역 드롭다운 tap ({cx}, {cy})") + self.driver.execute_script('mobile: tap', {'x': cx, 'y': cy}) + else: + self.driver.execute_script('mobile: tap', {'x': 187, 'y': 629}) + else: + self.click(self.get_selector('service_area_btn')) + print("서비스 영역 클릭") + self.wait_seconds(2) + if self.is_android(): + with open("ps_android_3_service_picker.xml", "w", encoding="utf-8") as f: + f.write(self.driver.page_source) + + def click_parking_radio(self): + if self.is_ios(): + self.find_element(self.get_selector('parking_radio')).click() + else: + self.click(self.get_selector('parking_radio')) + print("(신규)주차 라디오 클릭") + self.wait_seconds(1) + + def enter_title(self, text='자동화 테스트 제목'): + if self.is_ios(): + inputs = self.driver.find_elements(By.XPATH, '//XCUIElementTypeTextField') + if not inputs: + inputs = self.driver.find_elements(By.XPATH, '//XCUIElementTypeTextView') + el = inputs[0] + el.click() + el.send_keys(text) + else: + self.send_keys(self.get_selector('title_input'), text) + print(f"제목 입력: {text}") + self.wait_seconds(1) + + def enter_content(self, text='자동화 테스트 내용'): + if self.is_ios(): + inputs = self.driver.find_elements(By.XPATH, '//XCUIElementTypeTextView') + el = inputs[0] + el.click() + el.send_keys(text) + else: + self.send_keys(self.get_selector('content_input'), text) + print(f"내용 입력: {text}") + self.wait_seconds(1) + + def click_submit(self): + if self.is_ios(): + self.find_element(self.get_selector('submit_btn')).click() + else: + self.click(self.get_selector('submit_btn')) + print("등록하기 버튼 클릭") + self.wait_seconds(2) + + def click_confirm(self): + if self.is_ios(): + els = self.driver.find_elements(By.XPATH, '//XCUIElementTypeButton[@name="확인"]') + if not els: + els = self.driver.find_elements(By.XPATH, '//XCUIElementTypeOther[@name="확인"]') + if els: + cx = els[0].location['x'] + els[0].size['width'] // 2 + cy = els[0].location['y'] + els[0].size['height'] // 2 + self.driver.execute_script('mobile: tap', {'x': cx, 'y': cy}) + else: + self.click(self.get_selector('confirm_btn')) + print("확인 버튼 클릭") + self.wait_seconds(2) + + def full_inquiry_flow(self): + self.go_to_home_tab() + self.click_inquiry_banner() + self.click_branch_name() + self.select_first_branch() + self.click_service_area() + self.click_parking_radio() + self.swipe_up() + self.wait_seconds(1) + self.enter_title() + self.enter_content() + self.swipe_up() + self.wait_seconds(1) + self.click_submit() + self.click_confirm() + print("✅ 1:1문의 테스트 완료") + return True From 400f80cecbe5de508800426b03e24eaab5e3c6e7 Mon Sep 17 00:00:00 2001 From: jonghyeokFF Date: Wed, 6 May 2026 23:37:29 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(runner):=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoginTest, EntranceCardTest, EntranceCardIssueTest, LoungeTest, WithdrawTest, HomeInquiryTest 추가 Co-Authored-By: Claude Sonnet 4.6 --- fastfive-auto-app-dev/app_suite_runner.py | 147 +++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/fastfive-auto-app-dev/app_suite_runner.py b/fastfive-auto-app-dev/app_suite_runner.py index 054c40f..832a3a7 100644 --- a/fastfive-auto-app-dev/app_suite_runner.py +++ b/fastfive-auto-app-dev/app_suite_runner.py @@ -8,7 +8,7 @@ from appium import webdriver from appium.options.common import AppiumOptions from config import get_capabilities, APPIUM_URL, TEST_EMAIL, TEST_PASSWORD, PLATFORM -from pages import LoginPage, ReservationPage +from pages import LoginPage, ReservationPage, EntranceCardPage, EntranceCardIssuePage, LoungePage, MyAccountPage, HomeInquiryPage from request import run_inquiry_test # 영상 녹화 저장용 전역 변수 @@ -115,6 +115,27 @@ def addError(self, test, err): }) +class LoginTest(unittest.TestCase): + """로그인 테스트""" + driver = None + + def setUp(self): + capabilities = get_capabilities() + appium_options = AppiumOptions() + appium_options.load_capabilities(capabilities) + self.driver = webdriver.Remote(APPIUM_URL, options=appium_options) + self.platform = PLATFORM + + def tearDown(self): + if self.driver: + self.driver.quit() + + def test_login(self): + """로그인 테스트""" + login_page = LoginPage(self.driver, self.platform) + login_page.full_login_flow(TEST_EMAIL, TEST_PASSWORD) + + class ReservationTest(unittest.TestCase): driver = None _video_name = "" @@ -213,10 +234,134 @@ def test_inquiry(self): run_inquiry_test(driver, platform) +class EntranceCardTest(unittest.TestCase): + """출입카드 테스트""" + driver = None + + def setUp(self): + capabilities = get_capabilities() + appium_options = AppiumOptions() + appium_options.load_capabilities(capabilities) + self.driver = webdriver.Remote(APPIUM_URL, options=appium_options) + self.platform = PLATFORM + + def tearDown(self): + if self.driver: + self.driver.quit() + + def test_entrance_card(self): + """출입카드 탭 진입 후 카드 롱프레스""" + driver = self.driver + platform = self.platform + + login_page = LoginPage(driver, platform) + login_page.full_login_flow(TEST_EMAIL, TEST_PASSWORD) + + entrance_card_page = EntranceCardPage(driver, platform) + entrance_card_page.full_entrance_card_flow() + + +class EntranceCardIssueTest(unittest.TestCase): + """출입카드 생성 테스트""" + driver = None + + def setUp(self): + capabilities = get_capabilities() + appium_options = AppiumOptions() + appium_options.load_capabilities(capabilities) + self.driver = webdriver.Remote(APPIUM_URL, options=appium_options) + self.platform = PLATFORM + + def tearDown(self): + if self.driver: + self.driver.quit() + + def test_entrance_card_issue(self): + """출입카드 생성 버튼 클릭""" + login_page = LoginPage(self.driver, self.platform) + login_page.full_login_flow(TEST_EMAIL, TEST_PASSWORD) + + issue_page = EntranceCardIssuePage(self.driver, self.platform) + issue_page.full_entrance_card_issue_flow() + + +class LoungeTest(unittest.TestCase): + """라운지 탭 테스트""" + driver = None + + def setUp(self): + capabilities = get_capabilities() + appium_options = AppiumOptions() + appium_options.load_capabilities(capabilities) + self.driver = webdriver.Remote(APPIUM_URL, options=appium_options) + self.platform = PLATFORM + + def tearDown(self): + if self.driver: + self.driver.quit() + + def test_lounge(self): + """라운지 탭 진입 후 베네핏/커뮤니티/이벤트 탭 순서대로 클릭""" + login_page = LoginPage(self.driver, self.platform) + login_page.full_login_flow(TEST_EMAIL, TEST_PASSWORD) + + lounge_page = LoungePage(self.driver, self.platform) + lounge_page.full_lounge_flow() + + +class WithdrawTest(unittest.TestCase): + """회원탈퇴 테스트""" + driver = None + + def setUp(self): + capabilities = get_capabilities() + appium_options = AppiumOptions() + appium_options.load_capabilities(capabilities) + self.driver = webdriver.Remote(APPIUM_URL, options=appium_options) + self.platform = PLATFORM + + def tearDown(self): + if self.driver: + self.driver.quit() + + def test_withdraw(self): + """내 계정 → 프로필 편집 → 스크롤 → 회원탈퇴 → 체크박스 → 탈퇴하기""" + login_page = LoginPage(self.driver, self.platform) + login_page.full_login_flow(TEST_EMAIL, TEST_PASSWORD) + + my_account_page = MyAccountPage(self.driver, self.platform) + my_account_page.full_withdraw_flow() + + +class HomeInquiryTest(unittest.TestCase): + """홈 1:1문의 테스트""" + driver = None + + def setUp(self): + capabilities = get_capabilities() + appium_options = AppiumOptions() + appium_options.load_capabilities(capabilities) + self.driver = webdriver.Remote(APPIUM_URL, options=appium_options) + self.platform = PLATFORM + + def tearDown(self): + if self.driver: + self.driver.quit() + + def test_home_inquiry(self): + """홈 → 1:1문의 배너 → 서비스영역 → 주차 → 제목/내용 입력 → 등록하기""" + login_page = LoginPage(self.driver, self.platform) + login_page.full_login_flow(TEST_EMAIL, TEST_PASSWORD) + + inquiry_page = HomeInquiryPage(self.driver, self.platform) + inquiry_page.full_inquiry_flow() + + if __name__ == "__main__": suite = unittest.TestSuite() suite.addTest(ReservationTest("test_reservation")) suite.addTest(InquiryTest("test_inquiry")) + suite.addTest(EntranceCardTest("test_entrance_card")) result = TestResultCollector() runner = unittest.TextTestRunner(resultclass=lambda stream, descriptions, verbosity: result)