在測試中,系統登錄用到滑動驗證碼,根據系統驗證碼圖片的策略,分為有兩種定位模式;
左邊的圖是不帶缺口的,需要點擊拖動之后才有缺口模塊圖片出來;
右邊的是帶缺口的的背景圖,以及缺口滑塊的圖;
我們在自動化測試,拖動滑塊右移,主要難點就是確定缺口的橫坐標X;
兩種定位模式有啥區別呢?
主要體現在識別圖片上缺口的位置上;
左邊的識別方式是:保存無缺口的圖1和有缺口的圖2,對比兩張圖所有的RBG像素點,得到不一樣的像素點,得到缺口的坐標位置;
右邊的識別方式是:保存缺塊圖3和缺塊背景圖4,通過OpenCV提供了一個函數cv2.matchTemplate(),在較大背景圖像4中搜索和查找模板圖像3位置的方法。
我們系統用的是右邊的方式,在鼠標放到滑動驗證碼拖動塊上,圖片顯示出來,具體代碼如下:
from PIL import Image from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait import cv2 import numpy as np from io import BytesIO import time import requests import os class CrackSlider(): """ 通過瀏覽器截圖,識別驗證碼中缺口位置,獲取需要滑動距離,並模仿人類行為破解滑動驗證碼 """ def __init__(self): self.url = 'https://localhost/test/#/login' self.driver = webdriver.Firefox() self.wait = WebDriverWait(self.driver, 20) self.zoom = 1 def open(self): self.driver.get(self.url) def get_pic(self): time.sleep(2) # 因為驗證碼模塊需要鼠標位移上,才會顯示,所以為了方便,通過js修改了顯示屬性,讓元素可見 js = "document.getElementsByClassName('yidun_panel')[0].style.display='block';" # 調用js腳本 self.driver.execute_script(js) target = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'yidun_bg-img'))) template = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'yidun_jigsaw'))) target_link = target.get_attribute('src') template_link = template.get_attribute('src') target_img = Image.open(BytesIO(requests.get(target_link).content)) template_img = Image.open(BytesIO(requests.get(template_link).content)) target_img.save('target.jpg') template_img.save('template.png') local_img = Image.open('target.jpg') size_loc = local_img.size self.zoom = 320 / int(size_loc[0]) def crack_slider(self): slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'yidun_slider'))) ActionChains(self.driver).click_and_hold(slider).perform() for track in tracks['forward_tracks']: ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=0).perform() time.sleep(0.5) #for back_tracks in tracks['back_tracks']: # ActionChains(self.driver).move_by_offset(xoffset=back_tracks, yoffset=0).perform() ActionChains(self.driver).move_by_offset(xoffset=-4, yoffset=0).perform() ActionChains(self.driver).move_by_offset(xoffset=4, yoffset=0).perform() time.sleep(0.5) ActionChains(self.driver).release().perform() def get_tracks(self, distance): print(distance) distance += 20 v = 0 t = 0.2 forward_tracks = [] current = 0 mid = distance * 3 / 5 #減速閥值 while current < distance: if current < mid: a = 5 #加速度為+2 else: a = -3 #加速度-3 s = v * t + 0.5 * a * (t ** 2) v = v + a * t current += s forward_tracks.append(round(s)) back_tracks = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1] return {'forward_tracks': forward_tracks, 'back_tracks': back_tracks} def match(self, target, template): img_rgb = cv2.imread(target) img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) template = cv2.imread(template, 0) run = 1 w, h = template.shape[::-1] print(w, h) res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED) run = 1 # 使用二分法查找閾值的精確值 L = 0 R = 1 while run < 20: run += 1 threshold = (R + L) / 2 print(threshold) if threshold < 0: print('Error') return None loc = np.where(res >= threshold) print(len(loc[1])) if len(loc[1]) > 1: L += (R - L) / 2 print('目標區域起點x坐標為1:%d' % loc[1][0]) elif len(loc[1]) == 1: print('目標區域起點x坐標為2:%d' % loc[1][0]) break elif len(loc[1]) < 1: #print('目標區域起點x坐標為3:%d' % loc[1][0]) R -= (R - L) / 2 return loc[1][0] if __name__ == '__main__': cs = CrackSlider() cs.open() target = 'target.jpg' template = 'template.png' cs.get_pic() distance = cs.match(target, template) tracks = cs.get_tracks((distance + 7) * cs.zoom) # 對位移的縮放計算 cs.crack_slider()