首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python -继承:跨实例共享对象

Python -继承:跨实例共享对象
EN

Code Review用户
提问于 2021-02-17 10:55:16
回答 1查看 180关注 0票数 3

用例激励与挑战

大家好!过去两年,我一直在使用Python,但从未学习过正确的面向对象编程和设计模式。今年,我决定通过阅读一些书来弥补这一差距,并将这些知识应用到一个现实世界的问题上。我期待着从所有的建议中学到很多:)

为了开始我的学习,我决定每周自动完成一次重复的任务,填充位于Microsoft团队中的时间表,使用机器人为我完成繁重的工作。bot应执行以下步骤:

  • 导航到登录页面
  • 填写用户名和密码
  • 登录
  • 使用时间表导航到excel页面
  • 填写我每周的工作时间

目前,机器人几乎完成了所有步骤,除了最后两个步骤,我还没有实现这两个步骤。

码击穿

代码很简单。我非常依赖selenium来执行所有操作,因此我希望创建一个chrome实例,代理将在其中执行其操作。

自然,我首先导入将要使用的库:

代码语言:javascript
复制
import os
import time
import random

from selenium import webdriver
from dataclasses import dataclass
from abc import ABC, abstractmethod
from webdriver_manager.chrome import ChromeDriverManager

接下来,我定义了immutable类,其唯一目的是包含静态的信息,这样就可以避免代码重复。

代码语言:javascript
复制
@dataclass(frozen=True)
class XPathsContainer:
    teams_login_button: str = '//*[@id="mectrl_main_trigger"]/div/div[1]'
    teams_login_user_button: str = '//*[@id="i0116"]'
    teams_login_next_button: str = '//*[@id="idSIButton9"]'
    teams_login_pwd_button: str = '//*[@id="i0118"]'
    teams_sign_in_button: str = '//*[@id="idSIButton9"]'
    teams_sign_in_keep_logged_in: str = '//*[@id="KmsiCheckboxField"]'


@dataclass(frozen=True)
class UrlsContainer:
    teams_login_page: str = 'https://www.microsoft.com/en-in/microsoft-365/microsoft-teams/group-chat-software'

现在,我尝试实现一个名为Driver的基类。This类包含chrome对象的初始化,并设置继承的其他代理的基础。每个Agent子类可能(在将来)有不同的操作,但它们必须有一个睡眠方法(为了避免在使用机器人方面的限制),它们必须能够单击、写入信息和导航到页面。

代码语言:javascript
复制
class Driver(ABC):
    def __init__(self, action, instruction, driver=None):
        if driver:
            self.driver = driver
        else:
            self.driver = webdriver.Chrome(ChromeDriverManager().install())

        self.actions = {
            'navigate': self.navigate,
            'click': self.click,
            'write': self.write
        }

        self.parameters = {
            'action': None,
            'instruction': None
        }

    @abstractmethod
    def sleep(self, current_tick=1):
        pass

    @abstractmethod
    def navigate(self, *args):
        pass

    @abstractmethod
    def click(self, *args):
        pass

    @abstractmethod
    def write(self, **kwargs):
        pass

    @abstractmethod
    def main(self, **kwargs):
        pass

现在,我实现了一个基本的Agent child类,它实现了基类 Driver所需函数的逻辑。

代码语言:javascript
复制
class Agent(Driver):
    def __init__(self, action, instruction, driver):
        super().__init__(action, instruction, driver)
        self.action = action
        self.instruction = instruction

    def sleep(self, current_tick=1):
        seconds = random.randint(3, 7)
        timeout = time.time() + seconds
        while time.time() <= timeout:
            time.sleep(1)
            print(f"Sleeping to replicate user.... tick {current_tick}/{seconds}")
            current_tick += 1

    def navigate(self, url):
        print(f"Agent navigating to {url}...")
        return self.driver.get(url)

    def click(self, xpath):
        print(f"Agent clicking in '{xpath}'...")
        return self.driver.find_element_by_xpath(xpath).click()

    def write(self, args):
        xpath = args[0]
        phrase = args[1]
        print(f"Agent writing in '{xpath}' the phrase '{phrase}'...")
        return self.driver.find_element_by_xpath(xpath).send_keys(phrase)

    def main(self, **kwargs):
        self.action = kwargs.get('action', self.action)
        self.instruction = kwargs.get('instruction', self.instruction)
        self.actions[self.action](self.instruction)
        self.sleep()

最后,我创建了一个函数,每当有一组操作和指令需要在同一个铬驱动程序下执行时,就更新类的参数。我创建了一个函数,它接受一个动作脚本并执行它们。

代码语言:javascript
复制
def update_driver_parameters(driver, values):
    params = driver.parameters
    params['action'] = values[0]
    params['instruction'] = values[1]
    return params


def run_script(script):
    for script_line, script_values in SCRIPT.items():
        chrome = Agent(None, None, None)

        for instructions in script_values:
            params = update_driver_parameters(chrome, instructions)
            chrome.main(**params)
        chrome.sleep()


USER = os.environ["USERNAME"]
SECRET = os.environ["SECRET"]

SCRIPT = {
    'login': [
        ('navigate', UrlsContainer.teams_login_page),
        ('click', XPathsContainer.teams_login_button),
        ('write', (XPathsContainer.teams_login_user_button, USER)),
        ('click', XPathsContainer.teams_login_next_button),
        ('write', (XPathsContainer.teams_login_pwd_button, SECRET)),
        ('click', XPathsContainer.teams_sign_in_button),
        ('click', XPathsContainer.teams_sign_in_keep_logged_in),
        ('click', XPathsContainer.teams_sign_in_button),

    ]
}
run_script(SCRIPT)

关注

现在,我认为代码有几个主要问题,主要与缺乏设计模式的经验有关:

  • 我太依赖Xpath来让bot做一些事情,如果有很多步骤要做,就会产生一个巨大的数据类;
  • 此外,依赖Xpath可能是不好的,因为如果页面被更新,我将不得不重新跟踪步骤,但这可能是必要的邪恶;
  • 我不确定不可变类的实现是否正确。我用过dataclass来做这个;
  • 我觉得我实现的继承相当笨重。我希望能够与多个类共享相同的驱动程序。我不想为每个操作创建一个新的驱动程序,我总是希望获取驱动程序所做的最新上下文,但是如果创建了一个新的代理,那么必须为该代理分配一个新的驱动程序;
  • 也许kwargs参数可以以不同的方式实现,我从来不确定在不使用kwargs.get的情况下解析它们的正确方法;
  • 不一致地使用args和kwargs,这能以不同的方式实现吗?
EN

回答 1

Code Review用户

回答已采纳

发布于 2021-02-23 00:00:32

Bug:在run_script的第一行中,SCRIPT.items()应该是script.items()。正如所写的,它执行全局脚本,而不是函数的参数。

似乎代理不应该继承驱动程序

如果您研究Selenium最佳实践,您将发现一些对您的用例有意义的实践(大多数是针对测试的)。其中两个是Page对象和首选选择器顺序。

Page对象背后的想法是为web应用程序的每个页面创建一个类(或者至少是您正在使用的页面)。类封装了与该页面交互所需的数据和方法。然后,自动化脚本调用Page对象上的方法来自动化任务。例如,登录页面的类可能具有获取登录页面、输入用户名、输入密码、单击“记住我”复选框和单击登录按钮的方法。然后,登录方法按正确的顺序调用这些方法来进行登录。

这使您可以在一个地方隔离页面细节。例如,当前的设计似乎表明,如果您自动化另一个任务,则需要复制SCRIPTD4部分。然后,如果登录过程更改,每个脚本需要通过更新。使用Page对象,只需要更改登录页类。

实际上,选择元素的最可靠和最健壮的方法是ID,然后是名称、css选择器,最后是Xpath是最不健壮的。看起来大多数目标都有It,所以请使用它。

构造项目如下所示:

代码语言:javascript
复制
project
    pages
        __init__.py   # can be empty
        base.py       # one for each page 
        home.py
        login.py
        time.py
        ...etc...     # add whatever other pages you use

    entertime.py      # the script

然后

base.py

代码语言:javascript
复制
class BasePage:
    URL = None

    def __init__(self, driver=None):
        if driver is None:
            driver = webdriver.Chrome(ChromeDriverManager().install())
        
        self.driver = driver
        
    def click(self, locator, mu=1.5, sigma=0.3):
        """simulate human speed and click a page element."""
        self.dally(mu, sigma)
        self.driver.find_element(*locator).click()
        return self

    def dally(self, mu=1, sigma=0.2):
        pause = random.gauss(mu, sigma)
        while pause > 0:
            delta = min(1, pause)
            pause -= delta
            time.spleep(delta)
        return self

    def navigate(self):
        if self.URL:
            self.driver.get(self.URL)
            return self
            
        raise ValueError("No where to go.  No URL")
        
    def send_keys(self, locator, keys):
        self.driver.find_element(*locator).send_keys(keys)
        return self

login.py

代码语言:javascript
复制
from selenium import webdriver
from selenium.webdriver.common.by import By

from .base import BasePage
        
class LoginPage(BasePage):
    URL = 'https://www.microsoft.com/en-in/microsoft-365/microsoft-teams/group-chat-software'
    
    #locators for elements of the page
    LOGIN_BUTTON = (By.XPATH, '//*[@id="mectrl_main_trigger"]/div/div[1]')
    USERNAME_FIELD = (By.ID, "i0116")
    NEXT_BUTTON = (By.ID, "idSIButton9")
    PASSWORD_FIELD = (By.ID, "i0118")
    STAY_LOGGED_IN = (By.ID, "KmsiCheckboxField")
    
    def click_next(self):
        self.click(*self.NEXT_BUTTON)
        return self
        
    def start_login(self):
        self.click(*self.LOGIN_BUTTON)
        return self

    def enter_username(self, username):
        self.send_keys(*self.USERNAME_FIELD, username)
        self.click_next()
        return self
        
    def enter_password(self, password):
        self.send_keys(*self.PASSWORD_FIELD, password)
        self.click_next()
        return self
        
    def toggle_stay_logged_in(self):
        self.driver.find_element(*self.STAY_LOGGED_IN).click()
        return self
        
    def login(self, username, password):
        self.navigate()
        self.start_login()
        self.enter_username(username)
        self.enter_password(password)
        self.toggle_stay_logged_in()
        self.click_next()
    
        return HomePage(driver)   # or whatever page comes after a login

entertime.py

代码语言:javascript
复制
import os

from pages import LoginPage, HomePage   # what ever pages you need for the script

from selenium import webdriver
from selenium.webdriver.common.by import By

USER = os.environ["USERNAME"]
SECRET = os.environ["SECRET"]

homepage = LoginPage().login(USER, SECRET)

timepage = homepage.navigate_to_time_entry()  # <== whatever method you define
timepage.entertime()                          # <== whatever method you define

我没有MS团队来测试这个,所以这个还没有被测试过。这仅仅是一个建议,如何组织你的项目,使它更容易更新,扩展等。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/256135

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档