1. 程式人生 > >python2.7基於selenium的web自動化測試專案--base目錄檔案

python2.7基於selenium的web自動化測試專案--base目錄檔案

Base.py

#####

各公共方法的二次封裝以及定義,如拋棄selenium最外層的定位方法,直接傳入By類的變數,既避免過長的函式定義也規範了元素定位的變數定義;同時由於系統中部分控制元件用滑鼠點選不好定位,不能靈活
           的傳參,所以根據控制元件型別封裝各type_xx函式,呼叫js來替代原生方法;考慮到程式碼的執行速度遠高於瀏覽器的渲染速度以及網路載入速度,在隱式等待30秒的基礎上,基類中的定位方法在操作前都設定強制等待
           0.2s的時間,避免page頁中的每個操作後都加強制等待時間的繁瑣

 -*- coding:utf8 -*-#Create on : 2017 - 04 -24
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import
StaleElementReferenceException from selenium.common.exceptions import NoSuchElementException import logging import requests import re import time console = logging.StreamHandler() logging.basicConfig( level=logging.INFO, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s'
, datefmt='%Y-%m-%d %H:%M:%S', filename='log.log', filemode='w' ) console.setLevel(logging.INFO) formatter = logging.Formatter('%(message)s') console.setFormatter(formatter) logging.getLogger('').addHandler(console) def log(func): def wrapper(*args, **kwargs): info = func.__doc__ logging.info('testing at : %s' % info) return func(*args, **kwargs) return wrapper def errorLog(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except: logging.getLogger().exception('Exception') exit() return wrapper def consoleLog(info,level = 'INFO'): if level is 'INFO' : logging.info(info) elif level is 'WARNING': logging.warning(info) elif level is 'ERROR': logging.error(info) class Base(object): """ BasePage封裝所有頁面都公用的方法,例如driver, url ,FindElement等 """ def __init__(self, selenium_driver): """ 例項化WebDriver物件 :rtype: object :param selenium_driver: WebDriver物件 """ self.driver = selenium_driver succeed = 0 #用來接受每個新增函式執行成功並建立資料的數量,每成功一個就自增1,大於0的情況下,在測試結束或者報錯後根據值去刪除對應的已生成的資料 testCondition = ('test','mock','trunk') #待測試環境:測試、預發、線上 test = testCondition[0] @errorLog def open(self,url,loc,havaFrame = True): """ 重寫開啟地址函式 :param url: 目標地址 :param loc: 接收一個By物件的元組,用以確認頁面載入完成 :param havaFrame:是否有frame,預設為有(目前專案中頁面基本都存在frame),切換至最近的一個frame """ self.driver.get(url) if havaFrame == True: self.switch_frame(0) WebDriverWait(self.driver, 30).until(EC.presence_of_element_located(loc)) self.driver.maximize_window() # def repeat(self,func,*param): # for i in range(0,10): # try: # func(param) # break # except: # time.sleep(1) @errorLog def staleness_of(self,loc,index = None): """ 此方法主要適用情況如合同初審後,需要再次雙擊列表頁開啟進行復審的操作。 初審後有一個提示成功的對話方塊和panel關閉的延遲,視解析和網路情況導致時間不定,此時若馬上去雙擊會報StaleElementReferenceException,所以在此顯示等待 """ if type(index) == int: eles = self.find_elements(index, *loc) WebDriverWait(self.driver, 30).until(EC.staleness_of(eles)) else: ele = self.find_element(*loc) WebDriverWait(self.driver, 30).until(EC.staleness_of(ele)) @errorLog def wait_element(self,loc): """ 等待目標元素出現 :param loc: 接收一個By物件的元組,用以確認頁面載入完成 """ WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(loc),message=u'等待元素未出現,請檢視截圖') time.sleep(1) @errorLog def find_element(self,*loc): """ 返回單個元素定位 """ WebDriverWait(self.driver,30).until(EC.visibility_of_element_located(loc)) return self.driver.find_element(*loc) @errorLog def find_elements(self,index,*loc): """ 返回多個元素定位 :param index: 定位目標為陣列時,需指定所需元素在陣列中的位置 """ WebDriverWait(self.driver,30).until(EC.visibility_of_element_located(loc)) return self.driver.find_elements(*loc)[index] @errorLog def check_submit(self): """ 等待提交完成的提示出現,如新增或修改資料後的儲存成功等提示,以確認資料正常插入或更新。目前所有介面的提示存在時長為3s,所以等待3s PS:一開始加這麼長時間的硬性等待,我是拒絕的,但後面發現很多介面的操作必須要等待這個提示完全消失才可以,我嘗試了各種辦法也做不到完全避免,所以妥協了,硬等待就硬等待吧 :param loc: 提示的元素定位 """ loc = (By.CSS_SELECTOR, '.bootstrap-growl.alert.alert-info.alert-dismissible') WebDriverWait(self.driver,30).until(EC.presence_of_element_located(loc),u'沒有找到提交成功記錄') time.sleep(3) @errorLog def input_text(self, loc, text,first=True,index = None): """ 重寫send_keys方法 :param loc:目標元素 :param text:輸入值 :param first:預設為輸入框無內容,為False時則先清空再輸入 :param index:定位目標為陣列時,需指定所需元素在陣列中的位置 """ if first: if type(index) == int: eles = self.find_elements(index,*loc) for i in range(0,10): try: eles.click() break except WebDriverException: time.sleep(1) eles.send_keys(text) else: ele = self.find_element(*loc) for i in range(0, 10): try: ele.click() break except WebDriverException: time.sleep(1) ele.send_keys(text) else: if type(index) == int: eles = self.find_elements(index,*loc) for i in range(0, 10): try: eles.click() break except WebDriverException: time.sleep(1) eles.clear() eles.send_keys(text) else: ele = self.find_element(*loc) for i in range(0, 10): try: ele.click() break except WebDriverException: time.sleep(1) ele.clear() ele.send_keys(text) @errorLog def click(self, loc,index = None): """ 重寫click方法 :param index: 預設為定位單個元素點選,如定位返回陣列,則呼叫多個元素定位方法 """ #WebDriverWait(self.driver,10).until(EC.element_to_be_clickable(loc),u'元素不可點選') if type(index) == int: eles = self.find_elements(index,*loc) for i in range(0,10): try: eles.click() break except WebDriverException: time.sleep(1) else: ele = self.find_element(*loc) for i in range(0,10): try: ele.click() break except WebDriverException: time.sleep(1) @errorLog def dblclick(self,loc,index = None,checkLoc = None): """ 重寫雙擊方法 :param loc: 可執行雙擊的元素 (注:需核實元素是否有雙擊事件,如定位到tr中的某一個td時,雙擊是無效的,對tr的雙擊才有效。 是否有效,可在chrome的console中驗證,如$('#test').dblclick() :param index:預設為定位單個元素點選,如定位返回陣列,則呼叫多個元素定位方法 :param checkLoc:傳遞一個開啟後的介面中的元素,用以確認雙擊成功開啟詳情頁 """ if type(index) == int: for i in range(0,10): eles = self.find_elements(index, *loc) try: ActionChains(self.driver).double_click(eles).perform() if checkLoc != None: for i in range(0,10): try: self.driver.find_element(*checkLoc) break except NoSuchElementException: ActionChains(self.driver).double_click(eles).perform() break except StaleElementReferenceException: time.sleep(1) else: for i in range(0,5): ele = self.find_element(*loc) try: ActionChains(self.driver).double_click(ele).perform() if checkLoc != None: for i in range(0,10): try: self.driver.find_element(*checkLoc) break except NoSuchElementException: e = self.find_element(*loc) ActionChains(self.driver).double_click(e).perform() break except StaleElementReferenceException: time.sleep(1) @errorLog def context_click(self,loc,index = None): """ 重寫右擊方法 :param loc: 可執行右擊的元素 :param index: 預設為定位單個元素點選,如定位返回陣列,則呼叫多個元素定位方法 """ if type(index) == int: eles = self.find_elements(index,*loc) ActionChains(self.driver).context_click(eles).perform() else: ele = self.find_element(*loc) ActionChains(self.driver).context_click(ele).perform() @errorLog def switch_frame(self, loc): return self.driver.switch_to_frame(loc) @errorLog def script(self, js): """ 儘量少用js,因為執行速度遠遠高於網路和系統反應速度,很容易報錯。 迫不得已用到js的情況下無外乎點選、傳值等,如果太快頁面沒重新整理過來會導致報WebDriverException(目前已知會報出WebDriverException),此處捕獲後,等待1秒再次執行js,最多十次,若執行成功則跳出迴圈 :param js: :return: """ for i in range(1,11): try: self.driver.execute_script(js) break except WebDriverException,e: consoleLog(e) info = 'js執行失敗,正進行第%s次嘗試' % i consoleLog(info,level='WARNING') time.sleep(1) @errorLog def solr(self,core,condition,searchKey=None,searchValue=None): """ 測試完成後,目前是直接刪除資料,apartment和house的solr中的資料也需刪除 :param core: 需要刪除的core,如apartment-core、house-core :param condition: 當前環境,如test、mock、trunk :param searchKey: 查詢需要刪除的資料ID的條件,如residential_name,house_code等,做增量操作時,無需傳值 :param searchValue: 查詢需要刪除的資料ID的條件,對應searchKey,樓盤名稱為test,房源編號為xxxxx,做增量操作時,無需傳值 """ getKeyUrl = { 'house' : { 'test' : 'http://192.168.0.216:8080/solr/house_core/select?q=%s%%3A%s&fl=id&wt=json&indent=true&_=%s' % (searchKey,searchValue,str(time.time())), 'mock' : 'http://192.168.0.203:8080/solr/house_core/select?q=%s%%3A%s&fl=id&wt=json&indent=true&_=%s' % (searchKey,searchValue,str(time.time())), 'trunk' : 'http://121.40.105.35:8084/solr/house_core/select?q=%s%%3A%s&fl=id&wt=json&indent=true&_=%s' % (searchKey,searchValue,str(time.time())) }, 'apartment' : { 'test': 'http://192.168.0.216:8080/solr/apartment_core/select?q=%s%%3A%s&fl=id&wt=json&indent=true&_=%s' % (searchKey,searchValue,str(time.time())), 'mock' : 'http://192.168.0.203:8080/solr/apartment_core/select?q=%s%%3A%s&fl=id&wt=json&indent=true&_=%s' % (searchKey,searchValue,str(time.time())), 'trunk' : 'http://121.40.105.35:8084/solr/apartment_core/select?q=%s%%3A%s&fl=id&wt=json&indent=true&_=%s' % (searchKey,searchValue,str(time.time())) } } clearKeyUrl = { 'house': { 'test': 'http://192.168.0.216:8080/solr/house_core/update', 'mock': 'http://192.168.0.203:8080/solr/house_core/update', 'trunk': 'http://121.40.105.35:8084/solr/house_core/update' }, 'apartment': { 'test': 'http://192.168.0.216:8080/solr/apartment_core/update', 'mock': 'http://192.168.0.203:8080/solr/apartment_core/update', 'trunk': 'http://121.40.105.35:8084/solr/apartment_core/update' } } deltaImportUrl = { 'house': { 'test': 'http://192.168.0.216:8080/solr/house_core/dataimport?command=delta-import&commit=true&wt=json&indent=true&verbose=false&clean=false&optimize=false&debug=false', 'mock': 'http://192.168.0.216:8080/solr/house_core/dataimport?command=delta-import&commit=true&wt=json&indent=true&verbose=false&clean=false&optimize=false&debug=false', 'trunk': 'http://192.168.0.216:8080/solr/house_core/dataimport?command=delta-import&commit=true&wt=json&indent=true&verbose=false&clean=false&optimize=false&debug=false' }, 'apartment': { 'test': 'http://192.168.0.216:8080/solr/apartment_core/dataimport?command=delta-import&commit=true&wt=json&indent=true&verbose=false&clean=false&optimize=false&debug=false', 'mock': 'http://192.168.0.203:8080/solr/apartment_core/dataimport?command=delta-import&commit=true&wt=json&indent=true&verbose=false&clean=false&optimize=false&debug=false', 'trunk': 'http://121.40.105.35:8084/solr/apartment_core/dataimport?command=delta-import&commit=true&wt=json&indent=true&verbose=false&clean=false&optimize=false&debug=false' } } if searchKey is None and searchValue is None: requests.get(deltaImportUrl[core][condition]) text = requests.get(getKeyUrl[core][condition]).text if text.find('id') == -1: consoleLog('%s-core的solr資料已不存在,無需清理' % core) else: for key in re.findall('\"id\":\"(.*?)\"',text): key.encode('utf-8') payload = "<add commitWithin=\"1000\" overwrite=\"true\"><delete><id>%s</id></delete><commit/></add>" % key querystring = {"wt": "json"} headers = {"Content-Type":"text/xml"} result = requests.post(clearKeyUrl[core][condition],data=payload,headers=headers,params=querystring).text if result.find('0') == -1: consoleLog('%s-core的solr清理異常\n%s' % (core,result),level='ERROR') else: consoleLog('%s-core的solr資料已清理' % core) @errorLog def type_date(self, loc, dateValue): """ 定義type_date方法,用於處理日期控制元件的傳參 :param loc: 接收jquery的選擇器的值,如 #id .class css選擇器,不是page頁面中定義的元素元組 :param dateValue: 具體時間值,格式如2017-01-02 """ #js = "$(\"%s\").removeAttr('readonly');$(\"%s\").attr('value','%s')" % (loc,loc,date) #上面的是呼叫js原生方法,由於前段框架的問題,原生方法傳參無效,需利用jquery呼叫easyui中的方法 js = "$('%s').datebox('setValue','%s')" % (loc ,dateValue) self.script(js) time.sleep(0.2) @errorLog def type_select(self, loc, selectedValue): """ 定義type_select方法,用於處理下拉控制元件的傳參 :param loc: 接收jquery的選擇器的值,如 #id .class css選擇器,不是page頁面中定義的元素元組 :param selectedValue: 由於頁面在單擊選擇後,會傳給後臺key,而不是value,所以此處的傳參為資料字典中的Key,而非value!!! """ js = "$('%s').combobox('setValue','%s')" % (loc, selectedValue) self.script(js) time.sleep(0.2) @errorLog def type_combotree(self, loc, selectedValue): """ 定義type_combotree方法,用於處理系統中下拉為tree的控制元件,如部門的選擇 :param loc: 接收jquery的選擇器的值,如 #id .class css選擇器,不是page頁面中定義的元素元組 :param selectedValue: 由於頁面在單擊選擇後,會傳給後臺key,而不是value,所以此處的傳參為資料字典中的Key,而非value!!! """ js = r"$('%s').combotree('setValue','%s')" % (loc, selectedValue) self.script(js) time.sleep(0.2) @errorLog def type_checkbox(self, loc, ischecked): """ 定義type_checkbox方法,用於處理複選框控制元件的傳參 :param loc: 接收jquery的選擇器的值,如 #id .class css選擇器,不是page頁面中定義的元素元組 :param ischecked: 為boolean值 """ js = r"$('%s').attr('checked','%s')" % (loc, ischecked) self.script(js) time.sleep(0.2) @errorLog def type_click(self, loc): js = "$('%s').click()" % (loc) self.script(js)

#Page.py

###各web頁面的實際地址,攻其他pageClass中的操作方法呼叫

# -*- coding:utf8 -*-
#系統管理
userPage = 'http://isz.ishangzu.com/isz_base/jsp/user/userManage.jsp'     #使用者管理
resPage = 'http://isz.ishangzu.com/isz_base/jsp/user/resManage.jsp'     #資源管理
rolePage = 'http://isz.ishangzu.com/isz_base/jsp/user/roleManage.jsp'   #角色管理
depPage = 'http://isz.ishangzu.com/isz_base/jsp/user/departManage.jsp'      #部門管理
accreditPage = 'http://isz.ishangzu.com/isz_base/jsp/user/loginAuth.jsp'        #授權管理
positonPage = 'http://isz.ishangzu.com/isz_base/jsp/user/positionList.jsp'      #崗位管理
#客戶管理
customerListPage = 'http://isz.ishangzu.com/isz_customer/jsp/customer/customerList.jsp'    #租前客戶
customerFollowPage = 'http://isz.ishangzu.com/isz_customer/jsp/customer/customerFollowList.jsp'     #租客跟進
customerViewPage = 'http://isz.ishangzu.com/isz_customer/jsp/customer/customerViewList.jsp'     #租客帶看
#樓盤管理
areaPage = 'http://isz.ishangzu.com/isz_house/jsp/districtbusinesscircle/districtBusinessCircle.jsp'    #區域商圈
residentiaPage = 'http://isz.ishangzu.com/isz_house/jsp/residential/residentialList.jsp'    #樓盤字典
auditResidentiaPage = 'http://isz.ishangzu.com/isz_house/jsp/residential/auditResidentialList.jsp'  #樓盤維護
bussinessCirclePage = 'http://isz.ishangzu.com/isz_base/jsp/depbusinesscircle/depBussinessCircleList.jsp'   #門店商圈
transferPage = 'http://isz.ishangzu.com/isz_house/jsp/house/transfer/transferList.jsp'  #轉移房源
storePage = 'http://isz.ishangzu.com/isz_house/jsp/store/storeList.jsp'     #門店管理
storeMapPage = 'http://isz.ishangzu.com/isz_house/jsp/store/storeMap.jsp'   #門店樓盤
#房源管理
houseAddPage = 'http://isz.ishangzu.com/isz_house/jsp/house/develop/houseDevelopinfoAdd.jsp'    #新增房源
houseAuditPage = 'http://isz.ishangzu.com/isz_house/jsp/house/audit/houseAuditIndex.jsp'    #稽核房源
devHousePage = 'http://isz.ishangzu.com/isz_house/jsp/house/devhouse/houseIndex.jsp'   #開發自營房源
resHousePage = 'http://isz.ishangzu.com/isz_house/jsp/house/rent/houseIndex.jsp?from=manageHouseResource'   #資料房源
validHousePage = 'http://isz.ishangzu.com/isz_house/jsp/house/invalid/houseIndex.jsp'   #失效房源
truHousePage = 'http://isz.ishangzu.com/isz_house/jsp/house/trusteeship/houseIndex.jsp'      #託管中的房源
apartmentPage = 'http://isz.ishangzu.com/isz_house/jsp/apartment/apartmentIndex.jsp'    #自營房源
customerAptPage = 'http://isz.ishangzu.com/isz_house/jsp/apartment/apartmentList.jsp?for_customer=1'    #為客配房
#設計工程
designSharePage = 'http://isz.ishangzu.com/isz_house/jsp/design/designShareList.jsp'  # 品牌合租
designEntirePage = 'http://isz.ishangzu.com/isz_house/jsp/design/designEntireList.jsp'  # 品牌整租
designManageSharePage = 'http://isz.ishangzu.com/isz_house/jsp/design/designManageShareList.jsp' #託管合租
#合同管理
generalConractPage = 'http://isz.ishangzu.com/isz_contract/jsp/generalcontract/generallist.jsp'     #普單合同
entrustContractPage = 'http://isz.ishangzu.com/isz_contract/jsp/entrustcontract/contractList.jsp'       #委託合同
apartmentContractPage = 'http://isz.ishangzu.com/isz_contract/jsp/contract/apartmentContract.jsp'       #出租合同
apartmentAchievementPage = 'http://isz.ishangzu.com/isz_achievement/jsp/achievement/apartmentAchievementList.jsp'   #正常出房
backAchievementPage = 'http://isz.ishangzu.com/isz_achievement//jsp/achievement/back/contractAchievementBackList.jsp'       #結算扣回
vacancyAchievementPage = 'http://isz.ishangzu.com/isz_achievement/jsp/achievement/vacancy/apartmentVacancyAchievementList.jsp'      #空置虧損
defaultAchievementPage = 'http://isz.ishangzu.com/isz_achievement/jsp/achievement/default/apartmentDefaultAchievementList.jsp'      #違約業績
vacancyDatePage = 'http://isz.ishangzu.com/isz_achievement/jsp/achievement/vacancyList.jsp'     #空置日期變更表
contractEndPage = 'http://isz.ishangzu.com/isz_contract/jsp/end/end.jsp'      #終止結算
achievementPage = 'http://isz.ishangzu.com/isz_contract/jsp/contractachievement/achievementIndex.jsp'       #業績分成
achievementDepPage = 'http://isz.ishangzu.com/isz_contract/jsp/contractachievement/achievementDepList.jsp'      #部門業績排行榜
achievementUserPage = 'http://isz.ishangzu.com/isz_contract/jsp/contractachievement/achievementUserList.jsp'    #個人業績排行榜
#財務管理
houseContractPayPage = 'http://isz.ishangzu.com/isz_finance/jsp/house/housepay.jsp?tab=1&entrust_type='   #委託合同應付
apartmentContractPayPage = 'http://isz.ishangzu.com/isz_finance/jsp/contractReceivable/apartmentContractReceivableList.jsp'     #出租合同應收
reimbursementExpenseListPage = 'http://isz.ishangzu.com/isz_finance/jsp/expense/reimbursementExpenseList.jsp'   #報銷費用
reimbursementExpenseItemListPage = 'http://isz.ishangzu.com/isz_finance/jsp/expense/reimbursementExpenseItemList.jsp'   #報銷單詳情彙總
houseContractEndPayPage = 'http://isz.ishangzu.com/isz_finance/jsp/houseContractEnd/houseContractEndFlowShouldList.jsp?tab=8&type='     #委託合同終止結算收付
apartmentContractEndPayPage = 'http://isz.ishangzu.com/isz_finance/jsp/apartmentContractEnd/apartmentContractEndFlowShouldList.jsp?tab=3&type=&entrust_type='   #出租合同終止結算收付
depositToReceiptPage= 'http://isz.ishangzu.com/isz_finance/jsp/earnest/turnToReceiptList.jsp'   #定金轉入合同實收
depositToBreachPage= 'http://isz.ishangzu.com/isz_finance/jsp/earnest/turnToBreachList.jsp'   #定金轉入違約金

#SQL.py

###對資料庫的操作,目前只做對測試資料的清理

# -*- coding:utf8 -*-
#資料清理的基類,測試結束後呼叫此函式
import  pymysql
from base.Base import consoleLog
from base.Base import errorLog
from base.Base import Base


"""
按照業務邏輯順序以及主外來鍵約束,清理資料需按照以下順序執行,否則會造成主表資料刪除,其他關聯表資料刪不掉或者部分表刪除報錯。前面為模組名稱,後面為產生資料的表名
step1:user(sys_user_role、sys_user)
step2:residential(residential_buiding_no、residential_buiding_floor、residential_buiding_unit、residential_buiding、residential)
setp3:house(house_audit_status_change、house_configuration、house_develop_relation、follow_house、house_rent、house、house_develop_configuration、house_develop)
step4:house_contract(house_contract_payable、house_contract_rental_detail、house_contract_rent_detail,house_contract_rent_info,house_contract_sign,house_contract_res,house_contract_landlord,house_contract_attachment,house_contract)
step5:fitment(house_room_configuration、house_room_feature、house_room_func、house_room_tag、house_room、apartment、fitment_room、fitment_house)
setp6:apartment_contract()
"""
if Base.test == Base.testCondition[0]:
    conn = pymysql.connect(host='  ',user=' ',password='',db='',charset='utf8')
    cursor = conn.cursor()
if Base.test == Base.testCondition[1]:
    conn = pymysql.connect(host='',user='',password='',db=' ',charset='utf8')
    cursor = conn.cursor()
if Base.test == Base.testCondition[2]:
    conn = pymysql.connect(host='',user='',password='',db='',charset='utf8')
    cursor = conn.cursor()

sql = {
    #系統使用者
'user_01' : (
        "delete from sys_user_role where user_id in (select user_id from sys_user where user_name = 'AutoTest')",
"delete from sys_user where user_name = 'AutoTest'"
),
#樓盤字典棟座
'residential_02' : (
        "DELETE from residential where residential_name = 'AutoTest'",
"DELETE from residential_building where building_name = 'AutoTest'",
"DELETE from residential_building_unit where unit_name = 'AutoTest'",
"DELETE from residential_building_floor where floor_name = 'AutoTest'",
"DELETE from residential_building_house_no where house_no = 'AutoTest'"
),
#新增房源,稽核房源,開發自營房源的資料
'house_03' : (
        "DELETE from house_audit_status_change where house_id in (SELECT house_id from house where residential_id in (SELECT residential_id from residential where residential_name = 'AutoTest'))",
"DELETE from house_configuration where house_id  in (SELECT house_id from house where residential_id in (SELECT residential_id from residential where residential_name = 'AutoTest'))",
"DELETE from house_develop_relation where house_id  in (SELECT house_id from house where residential_id in (SELECT residential_id from residential where residential_name = 'AutoTest'))",
"DELETE from follow_house where object_id in (SELECT house_id from house where residential_id in (SELECT residential_id from residential where residential_name = 'AutoTest'))",
"DELETE from house_rent where residential_id in (SELECT residential_id from residential where residential_name = 'AutoTest')",
"DELETE from house where residential_id in (SELECT residential_id from residential where residential_name = 'AutoTest')",
"DELETE from house_develop_configuration where house_develop_id in (SELECT house_develop_id from house_develop where residential_name = 'AutoTest')",
"DELETE from house_develop where residential_name = 'AutoTest(auto)'"
),
#委託合同
'house_contract_04' : (
        "DELETE from house_contract_payable where contract_id in (SELECT contract_id from house_contract where contract_num = 'AutoTest')",
"DELETE from house_contract_rental_detail