当前位置: 首页 > news >正文

php-webdriver 通过队列的方式实现工作流

最近需要批量操作某个古老的后台,但是流程非常复杂,通过 php-webdriver/webdriver 写了个基于队列的方式实现工作流;

可以帮你实现web ui 测试, 自动登录站点,自动识别验证码(不复杂的那种),自动操作等,

首先使用docker-compose搭建selenium测试平台,其中ocr是用于对验证码的识别

version: '3.3'
networks:net:driver: bridge
services:chrome:image: selenium/standalone-chrome:4.15.0-20231129container_name: chromehostname: ypprivileged: trueshm_size: 4gports:- 4443:4444- 5901:5900- 7902:7900volumes:- ./public/uploads/goods:/home/seluser/Downloadsnetworks:- netedge:image: selenium/standalone-edge:4.15.0-20231129container_name: edgehostname: ypprivileged: trueshm_size: 4gports:- 4441:4444- 5902:5900- 7901:7900volumes:- ./public/uploads/goods:/home/seluser/Downloadsnetworks:- netocrapi:container_name: ocrapibuild:context: ./dockerdockerfile: Dockerfilevolumes:- ./docker/ocr.py:/ocrapi/ocr.pyports:- "5000:5000"networks:- net

dockerfile

FROM python:3.11 # 类似ADD,将我们文件拷贝到镜像中
WORKDIR /app 
# 镜像的工作目录
RUN ls \&& pip install -i  https://mirrors.aliyun.com/pypi/simple/ ddddocr==1.4.11 \&& pip install -i  https://mirrors.aliyun.com/pypi/simple/ Flask==3.0.2
# RUN pip install -r ./requirements.txt 
# 镜像构建的时候需要运行的命令
EXPOSE 5000
# 暴露端口配置 实际取决于docker run 命令的选择
ENTRYPOINT ["python"] 
# 基础镜像,构建镜像的开始,这里是python版本
COPY . /app 
# 指定这个容器启动的时候要运行的命令,可以追加命令
CMD ["/app/ocr.py"] 
# #指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代

 ocr.py

from flask import Flask, request, jsonify
import ddddocrapp = Flask(__name__)# 初始化OCR
ocr = ddddocr.DdddOcr()@app.route('/ocr', methods=['POST'])
def ocr_endpoint():try:# 获取上传的图片数据img_data = request.files['image'].read()# 调用OCR识别图片result = ocr.classification(img_data)# 返回识别结果return jsonify({'text': result})except Exception as e:# 发生错误时返回错误信息return jsonify({'error': str(e)}), 400if __name__ == '__main__':app.run(host='0.0.0.0',debug=True)

以上是环境搭建,ocr按目录载入即可,docker自动构建部署;

ocr是基于容器实现的,通过接口的方式调用,也可以独立使用;

接下来就是核心工具trait ,需要创建一个自定义命令,然后use这个trait 

<?phpnamespace app\yperp\common;use Facebook\WebDriver\Interactions\WebDriverActions;
use think\facade\Cache;
use GuzzleHttp\Client;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverSelect;
use InvalidArgumentException;
use RuntimeException;trait MyWebDriver
{use Docker;protected $output;protected $input;protected $driver = null;protected $windows = [];protected $host = 'http://localhost:4443/wd/hub';protected $cookies = [];private $sessionIdKey = 'webdriver_session_id';protected function WebDriver(){$sessionId = Cache::get($this->sessionIdKey);if ($sessionId) {try {$this->output->writeln('使用$sessionId创建会话:' . $sessionId);$this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->host, 300, 300, true, $this->config());if ($this->driver->getCurrentURL() === 'about:blank') {Cache::set($this->sessionIdKey, false);$this->driver->quit();$this->driver = $this->createNewWebDriver();}$this->driver->manage()->window()->maximize();$this->output->writeln('创建会话完成');} catch (\Exception $e) {Cache::set($this->sessionIdKey, false);$this->driver = $this->createNewWebDriver();}} else {$this->driver = $this->createNewWebDriver();}return $this->driver;}private function createNewWebDriver(){$this->output->writeln('创建会话');Cache::set($this->sessionIdKey, false);$driver = RemoteWebDriver::create($this->host, $this->config());$this->driver->manage()->window()->maximize();$sessionId = $driver->getSessionID();$this->output->writeln('$sessionId:' . $sessionId);Cache::set($this->sessionIdKey, $sessionId);$this->output->writeln('创建会话完成');return $driver;}function config(){$capabilities = DesiredCapabilities::chrome();$capabilities->setCapability('goog:chromeOptions',['prefs' => ['download.default_directory' => '/home/seluser/Downloads','download.prompt_for_download' => false,'download.directory_upgrade' => true,'safebrowsing.enabled' => true,],]);return $capabilities;}protected function setCookies(){if ($this->cookies  !== []) {$this->output->writeln('cookie登录');foreach ($this->cookies as $cookie) {$this->driver->manage()->addCookie($cookie);}}}function ask($tps = '请输入'){if (ob_get_level() > 0) {ob_flush();}fwrite(STDOUT, "$tps");return     trim(fgets(STDIN));}private function ocrCaptchaOnline($imgPath){$this->output->writeln('验证码接口');$Http =   new Client();$res =    $Http->post('http://api.waizaowang.com/ai/captcha', ['headers' => ['Content-Type' => 'application/x-www-form-urlencoded',],'form_params' => ['models' => 1003,'token' => '6554470371eba99bdaa27b265adf3fc4','image' => base64_encode(file_get_contents($imgPath)),],])->getBody()->getContents();$this->output->writeln('验证码接口结果:' . $res);$res = json_decode($res, true);if ($res['code'] == 200 &&  $res['data'][0]['succsss']) {return $res['data'][0]['result'];} else {return false;}}protected function docker_restart(){Cache::set('$SessionId', false);Cache::set('$getWindowHandle', false);$this->output->writeln('重启容器');static::docer_containers_restart('chrome');$isRun = false;while ($isRun) {try {static::docer_check_run('chrome');$isRun = true;} catch (\Throwable $th) {//throw $th;}sleep(1);}sleep(5);$this->output->writeln('启动成功');}protected function click_check_elem(WebDriverBy $select, $wait = 3){$this->output->writeln('操作元素:' . $select->getValue());$Elemensts = $this->driver->findElements($select);if (!empty($Elemensts)) {$imgElement = $this->driver->findElement($select);$imgElement->click();$this->output->writeln('完成操作元素:' . $select->getValue());sleep($wait);} else {$this->output->writeln('操作失败' . $select->getValue());}}protected  function toRun(){$number = 0;Cache::set('OptionsStatus', 1);while (Cache::get('OptionsStatus') >= 1) {if ($this->driver  == null) {$this->driver  = $this->WebDriver();}sleep(1);if ($number > 120) {$this->driver->findElement(WebDriverBy::xpath('html'));$number = 0;} else {$number++;}try {$options = Redis::lpop();$this->runSetp($options);} catch (\Throwable $th) {$this->output->writeln('操作错误:' . $th->getMessage());$this->output->writeln('操作错误:' . $th->getTraceAsString());if (isset($options['error']) && in_array($options['error'], [])) {$type = $options['error'];$this->$type();}}}}function ocrApi($imgPath,   $apiUrl = 'http://127.0.0.1:5000/ocr'){// 检查文件是否存在if (!file_exists($imgPath)) {throw new InvalidArgumentException("File not found at path: {$imgPath}");}// 初始化cURL会话$ch = curl_init();// 设置cURL选项curl_setopt_array($ch, [CURLOPT_URL => $apiUrl,CURLOPT_POST => true,CURLOPT_RETURNTRANSFER => true,CURLOPT_POSTFIELDS => ['image' => new \CurlFile($imgPath)],CURLOPT_HTTPHEADER => ['Content-Type: multipart/form-data'],]);// 执行cURL请求$response = curl_exec($ch);// 检查cURL错误if (curl_errno($ch)) {throw new RuntimeException('cURL error: ' . curl_error($ch));}// 获取HTTP响应状态码$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);// 关闭cURL会话curl_close($ch);// 根据HTTP响应状态码处理结果if ($httpCode !== 200) {throw new RuntimeException('Failed to upload image. Server responded with status code: ' . $httpCode);}// 返回响应内容return json_decode($response, true);}function open($do){$this->driver->get($do['url']);}/*** 执行函数*/function func($do){$this->output->writeln('执行函数:' . $do['func']);$func =  $do['func'];$func($this, $do);}/*** 执行类方法*/function this($do){$this->output->writeln('执行当前类方法:' . $do['this_method']);$method = $do['this_method'];$this->$method($this, $do);}function class($do){call_user_func_array([$do['className'], $do['func']], $do['args']);}function window($do){$option = $do['option'];$this->$option($do['value']);}function closeWindow(){// 获取所有窗口的句柄$windowHandles =  $this->driver->getWindowHandles();// 关闭新窗口$this->driver->switchTo()->window($windowHandles[count($windowHandles) - 1])->close();$this->output->writeln('关闭窗口');// 返回到原来的窗口$this->driver->switchTo()->window($windowHandles[0]);}function toWindow($windowId){// 获取所有窗口句柄$windowHandles =  $this->driver->getWindowHandles();if (isset($windowHandles[$windowId])) {$this->driver->switchTo()->window($windowHandles[$windowId]);$this->output->writeln('窗口切换成功:' . $windowId);} else {$this->output->writeln('窗口不存在:' . $windowId);}}function getSelect($do){$keys = array_keys($do);$by = null;if (in_array('id', $keys)) {$by = 'id';} else if (in_array('name', $keys)) {$by = 'name';} else if (in_array('className', $keys)) {$by = 'className';} else if (in_array('cssSelector', $keys)) {$by = 'cssSelector';} else if (in_array('linkText', $keys)) {$by = 'linkText';} else if (in_array('partialLinkText', $keys)) {$by = 'partialLinkText';} else if (in_array('tagName', $keys)) {$by = 'tagName';} else if (in_array('xpath', $keys)) {$by = 'xpath';}return     WebDriverBy::$by($do[$by]);;}function findElement($do){if (is_string($do['option'])) {$this->options($do, $do['option']);} else {foreach ($do['option'] as $option => $value) {$this->options($do, $option, $value);}}}function options($do, $option, $value = null){$select = $this->getSelect($do);$element = $this->driver->findElement($select);switch ($option) {case 'sendKeys':$this->output->writeln('sendKeys:' . $value);$element->sendKeys($value);break;case 'uploadFiles':if (is_string($option)) {$value[] = $option;}if (is_array($value)) {// 将文件路径设置为 input 元素的值// 上传多张图片foreach ($value as $imagePath) {// $element->setFileDetector(new LocalFileDetector());$this->output->writeln('sendKeys:' . $imagePath);try {$element->sendKeys($imagePath);} catch (\Throwable $th) {//throw $th;}sleep(1);}}break;case 'select':// 使用 WebDriverSelect 类来操作下拉框$select = new WebDriverSelect($element);switch ($do['type']) {case 'text':$select->selectByVisibleText($do['value']);break;case 'value':$select->selectByValue($do['value']);break;case 'index':$select->selectByIndex($do['value']);break;default:$select->selectByVisibleText($do['value']);}break;case 'click':$element->click();break;case 'doubleClick':// 创建一个Actions实例$actions = new WebDriverActions($this->driver);// 执行双击操作$actions->doubleClick($element)->perform();break;case 'Keys':$constant = constant('Facebook\WebDriver\WebDriverKeys::' . $value);if ($constant) {$element->sendKeys($constant);} else {$this->output->writeln('不存在的按键操作:Facebook\WebDriver\WebDriverKeys::' . $value);}break;case 'clear':$this->output->writeln('clear');$element->clear();break;case 'sleep':sleep($value);break;case 'script':$this->driver->executeScript($value, [$element]);default:}}function run_script($do){$scripts = $do['option'];if (is_string($scripts)) {$scripts[] = $scripts;}if (is_array($scripts)) {foreach ($scripts as $key => $script) {$this->driver->executeScript($script);}}}function frame($do){$option = $do['option'];switch ($option) {case 'inframe':$this->inframe($do);break;case 'outframe':$this->outFrame();break;case 'parentFrame':$this->parentFrame();break;default:$this->output->writeln('操作错误:');}}function inframe($do){$select = $this->getSelect($do);$iframeElement = $this->driver->findElement($select);$this->driver->switchTo()->frame($iframeElement);}function outFrame(){$this->driver->switchTo()->defaultContent();}function parentFrame(){$this->driver->switchTo()->parentFrame();}function countChildElement($do){$select = $this->getSelect($do);$parentElement = $this->driver->findElement($select);// 找到父元素下的所有子元素$childElements = $parentElement->findElements(WebDriverBy::tagName('*'));// 获取所有元素的个数return  count($childElements);}function checkNumberDo($do){$this->output->writeln('开始执行:checkNumberDo');$func = $do['func'];$number = (int)$func($this->driver, $do);$this->output->writeln('获取元素数量:' . $number);if ($number) {$emptyArrays = array_fill(0, $number, []);$options = $do['setps'] ?: false;foreach ($emptyArrays as $null) {if (is_array($options)) {foreach ($options as $option) {try {$this->runSetp($option);} catch (\Throwable $th) {//throw $th;}}}}}}function checkIsset($do){// $select =  $this->getSelect($do);// $parentElements =  $this->driver->findElements($select);// $this->output->writeln('判断元素是否存在');// if (!empty($parentElements)) {//     $this->output->writeln('元素存在,开始执行对比程序');$func = $do['func'];if ((bool)$func($this->driver, $do)) {$this->output->writeln('对比成功');$options = $do['true'] ?: false;} else {$this->output->writeln('对比失败');$options = $do['false'] ?: false;}if ($options) {foreach ($options as $option) {$this->runSetp($option);}}}protected  function runSetp($option){if ($option) {$this->output->writeln('-------------------------------------------');$this->output->writeln('时间:' . date('Y-m-d H:i:s', time()));if (isset($option['title'])) {$this->output->writeln('操作名称:' . $option['title']);}$this->output->writeln('操作类型:' . $option['type']);$this->output->writeln('操作数据:' . json_encode($option, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));$type = $option['type'];$this->$type($option['do']);if (isset($option['sleep'])) {sleep($option['sleep']);}}}protected function rpush($Options){$redis = new \Redis();$redis->connect('127.0.0.1', '6379');$password = 'fenglove';$redis->auth($password);foreach ($Options as $k => $Option) {$redis->rpush('Options', json_encode($Option));}}protected function lpop(){$redis = new \Redis();$redis->connect('127.0.0.1', 6379);$password = 'fenglove';$redis->auth($password);//list类型出队操作$value = $redis->lpop('Options');if ($value) {return json_decode($value, true);} else {return false;}}
}

 docker是通过docker的api来管理容器的重启的

<?phpdeclare(strict_types=1);namespace app\yperp\common;trait Docker
{/*** docker api* @param $url* @param string $method* @return mixed* @throws \GuzzleHttp\Exception\GuzzleException* @throws \Psr\SimpleCache\InvalidArgumentException* @throws \RedisException* @throws \think\db\exception\DataNotFoundException* @throws \think\db\exception\DbException*/static function docker_api($url, $method = "GET"){// var_dump($url);$url = 'http://localhost:2375' . $url;$ch = curl_init($url);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch, CURLOPT_HEADER, 0);curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);$response = curl_exec($ch);curl_close($ch);return json_decode($response, true);}/*** 容器重启*/static  function docer_containers_restart($containerId){return static::docker_api("/containers/{$containerId}/restart", 'POST');}/*** 获取硬气状态*/static function docer_containers_info($containerId){return static::docker_api("/containers/{$containerId}/json");}/*** 检查容器是否启动*/static function docer_check_run($containerId){$containers = static::docer_containers_info($containerId);return ($containers['State']['Status'] == 'running') ? true : false;}
}

 命令行实例:

<?phpnamespace app\yperp\command;use app\yperp\common\MyWebDriver;
use app\yperp\common\Redis;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;class mgtpush extends Command
{use MyWebDriver;protected function configure(){// 指令配置$this->setName('pushserver')->addArgument('restart', Argument::OPTIONAL, "重启docker容器")->setDescription('推送数据到系统');}protected function execute(Input $input, Output $output){// 指令输出$output->writeln('pushserver');// 指令输出$this->input = $input;$this->output = $output;$restart = $this->input->getArgument('restart');if ($restart) {Redis::ldelete();$this->docker_restart();}$this->output->writeln('启动完成');// $this->driver->get('https://yjx.jzzw-tech.cn/test.html');$this->output->writeln('等待操作');$this->toRun();}
}

下面是生成工作流的数组实例,可按需使用,按顺序推送到队列,即可按操作执行;

<?phpdeclare(strict_types=1);namespace app\yperp\common;class List
{static public function docker_restart(){return [// ['type' => 'this', 'do' => ['this_method' => 'docker_restart'], 'sleep' => '3'],];}static public function login(){return [['type' => 'this', 'do' => ['this_method' => 'run_login'], 'sleep' => '3'],];}static public function checklogin(){return [['type' => 'this', 'do' => ['this_method' => 'run_login'], 'sleep' => '3'],];}static public function list(){return [['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath'  => '/html/body/div[8]/div[2]/div[1]/div[2]/iframe'], 'sleep' => 1],['type' => 'findElement',   'do' => ['option' => 'click', 'xpath' => '/html/body/div/div[2]/div/div[4]/a',], 'sleep' => 1],['type' => 'frame', 'do' => ['option' => 'outframe',], 'sleep' => 3],['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath' => '/html/body/div[8]/div[2]/div[3]/iframe'], 'sleep' => 1],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[2]/div/div[1]', 'option' => 'doubleClick'], 'sleep' => 1],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[2]/div/div[1]', 'option' => 'doubleClick'], 'sleep' => 1],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[2]/div/div[1]', 'option' => 'doubleClick'], 'sleep' => 1],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[2]/div[2]/div[1]', 'option' => 'doubleClick']],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[2]/div[2]/div[1]', 'option' => 'doubleClick']],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[3]/table/tbody/tr/td[1]', 'option' => 'doubleClick']],];}static public function nav($navs = []){$step[] =  ['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath'  => '/html/body/div[8]/div[2]/div[1]/div[2]/iframe'], 'sleep' => 1];$step[] = ['type' => 'findElement',   'do' => ['option' => 'click', 'cssSelector' => 'div[title="资源库"]',], 'sleep' => 1];$step[] = ['type' => 'findElement',   'do' => ['option' => 'click', 'xpath' => '//div[@title="产品中心" and @classpre="site"]',], 'sleep' => 1];$step[] = ['type' => 'findElement',   'do' => ['option' => 'click', 'xpath' => '//div[@title="产品中心" and @classpre="channel"]',], 'sleep' => 1];foreach ($navs as $key => $nav) {$step[] = ['type' => 'findElement',   'do' => ['option' => 'click', 'cssSelector' => 'div[title="' . $nav . '"]',]];$step[] = ['type' => 'findElement',   'do' => ['option' => 'click', 'cssSelector' => 'div[title="' . $nav . '"]  a',]];}$step[] =  ['type' => 'frame', 'do' => ['option' => 'outframe',], 'sleep' => 3];$step[] =  ['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath' => '/html/body/div[8]/div[2]/div[3]/iframe'], 'sleep' => 1];return $step;}static function checkIsset($goods_code){return [['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[1]/div/div[3]/div/div/table/tbody/tr/td[1]/input', 'option' => 'click'],],['title' => '搜索物料编码','type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[1]/div/div[3]/div/div/table/tbody/tr/td[1]/input','option' => ['sendKeys' => $goods_code,]],'sleep' => 1],['type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[1]/div/div[3]/div/div/table/tbody/tr/td[2]/div', 'option' => 'click'], 'sleep' => 3],['title' => '判断物料编码是否存在','type' => 'checkIsset','do' =>  ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[1]/table/tbody[2]/tr/td','func' => 'checkAttrClassName','true' => static::editPage(),'false' => static::addPage(),],'sleep' => '3'],];}static public function addPage(){return [//点击进入创建商品页面['title' => '打开添加窗口', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[2]/div[1]/div[2]/div[1]/div/div[1]/div/div[2]', 'option' => 'click']],['type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 1], 'sleep' => 3],];}static public function editPage(){return [//点击进入创建商品页面['title' => '打开编辑窗口', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[1]/div[1]/div[2]/div[1]/table/tbody[2]/tr/td[2]/span', 'option' => 'click']],['type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 1], 'sleep' => 3],];}static function status($info){return [['type' => 'class', 'do' => ['className' =>  'app\yperp\model\Supplier', 'func' => 'status', 'args' => [$info['id'], 2]], 'sleep' => 3],];}static public function saveDataTest($array = []){return [//图片管理['title' => '切换tab 图片管理', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/ul/li[11]/a', 'option' => 'click',], 'sleep' => 3],['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath'  => '/html/body/form/div/div/div[11]/div/div/div/iframe']],['title' => '显示上传按钮', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[4]/div[1]/div/input[2]','option' => ['script' => "arguments[0].setAttribute('style', 'display: block;')",],], 'sleep' => 3],//商品头图['title' => '上传商品头图', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[4]/div[1]/div/input[2]','option' => ['uploadFiles' => $array['goods_header_imgs']],], 'sleep' => 3],// /html/body/div[4]/div[2]/div/div[1]/div[3]/span[3]['type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 1], 'sleep' => 3],];}static public function saveGoodsHeaderImgs($array = []){$setp = [];//图片管理$setp[] = ['title' => '切换tab 图片管理', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/ul/li[11]/a', 'option' => 'click',], 'sleep' => 3];$setp[] = ['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath'  => '/html/body/form/div/div/div[11]/div/div/div/iframe']];$setp[] = ['title' => '显示上传按钮', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[4]/div[1]/div/input[2]','option' => ['script' => "arguments[0].setAttribute('style', 'display: block;')",],], 'sleep' => 3];//  /html/body/div[4]/div[2]/div$setp[] =['title' => '根据数量执行操作步骤','type' => 'checkNumberDo','do' =>  ['xpath' => '/html/body/div[4]/div[2]/div','func' => 'checkElementNumber','setps' => [// /html/body/div[4]/div[2]/div/div[1]/div[3]/span[3]['title' => '删除商品头图', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[4]/div[2]/div/div[1]/div[3]/span[3]', 'option' => 'click']],],],'sleep' => '3'];//商品头图foreach ($array['goods_header_imgs'] as $key => $goods_header_imgs) {# code...$setp[] =   ['title' => '上传商品头图', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[4]/div[1]/div/input[2]','option' => ['clear' => 'clear','uploadFiles' => [str_replace('/\s+/', '', $goods_header_imgs)],],], 'sleep' => 3];}$setp[] =  ['type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 1], 'sleep' => 3];return $setp;}static public function saveGoodsBodyImgs($array = []){$setp = [];//产品特征$setp[] =   ['title' => '切换tab 产品特征', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/ul/li[8]/a', 'option' => 'click',], 'sleep' => 3];$setp[] = ['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath'  => '/html/body/form/div/div/div[8]/div/div/div/iframe']];$setp[] =   ['type' => 'frame', 'do' => ['option' => 'inframe', 'xpath'  => '/html/body/iframe[7]']];$setp[] =    ['title' => '点击代码按钮', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[2]/div[3]/div[1]', 'option' => 'click',], 'sleep' => 3];$setp[] =   ['title' => '清空代码', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[2]/div[2]/div/table/tbody/tr[2]/td[2]/div/textarea', 'option' => ['clear' => 'clear'],], 'sleep' => 3];$setp[] =    ['title' => '点击设计按钮', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[2]/div[3]/div[2]', 'option' => 'click',], 'sleep' => 3];$setp[] =   ['title' => '点击多图上传打开新窗口', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[2]/div[1]/table/tbody/tr[2]/td[3]/table[1]/tbody/tr/td[23]/div', 'option' => 'click',], 'sleep' => 1];$setp[] =    ['type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 2], 'sleep' => 3];$setp[] =  ['title' => '显示详情图上传按钮', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[2]/div[3]/div/input','option' => ['script' => "arguments[0].setAttribute('style', 'display: block;')",],], 'sleep' => 3];//商品详情图foreach ($array['goods_body_imgs'] as $key => $goods_body_img) {$setp[] =  ['title' => '上传商品详情图', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[2]/div[3]/div/input','option' => ['clear' => 'clear','uploadFiles' => [str_replace('/\s+/', '', $goods_body_img)],],],];}$setp[] =  ['title' => '确认上传', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/div[4]/div[1]', 'option' => 'click',], 'sleep' => 3];$setp[] =  ['type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 1], 'sleep' => 3];return $setp;}static function saveGoodsMainImgs($array){return [//商品头图['title' => '判断主图是否存在','type' => 'checkIsset','do' =>  ['xpath' => '/html/body/form/div/div/div[1]/div[2]/div[3]/div/form/div[4]','func' => 'checkAttrClassName','true' => [['title' => '删除已经存在的商品头图', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[2]/div[3]/div/form/div[4]/div/span[2]', 'option' => 'click']],],],'sleep' => '3'],// 如果 /html/body/form/div/div/div[1]/div[2]/div[3]/div/form/div[4] 中有两个值,那么将删除后重新再上传['title' => '新增商品头图', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[2]/div[3]/div/form/div[1]/input','option' => ['sendKeys' => $array['goods_main_img']]], 'sleep' => 3],];}static public function  closeWindow(){return [//保存并关闭['title' => '保存并关闭', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[13]/button[1]',  'option' => 'click',], 'sleep' => 2],['title' => '关闭提示确认', 'type' => 'findElement', 'error' => ['option' => 'closeWindow',], 'do' => ['xpath' => '/html/body/div/div[2]/div/div/table/tbody/tr[2]/td/div/div/div/a',  'option' => 'click',], 'sleep' => 2],// ['title' => '关闭窗口', 'type' => 'findElement', 'do' => ['xpath' => ' /html/body/form/div/div/div[13]/button[3]',  'option' => 'click',], 'sleep' => 2],['title' => '关闭窗口', 'type' => 'window', 'do' => ['option' => 'closeWindow', 'value' => 1], 'sleep' => 3],['title' => '回到主窗口', 'type' => 'window', 'do' => ['option' => 'toWindow', 'value' => 0], 'sleep' => 3],// ['title' => '关闭窗口', 'type' => 'window', 'do' => ['option' => 'closeWindow', 'value' => 0], 'sleep' => 3],];}static public function saveData($array = []){if (!isset($array['weight'])) {$array['weight'] = '8';}if (!isset($array['weight_unit'])) {$array['weight_unit'] = 1;}$array['goods_title'] = $array['goods_title_alias'];$array['goods_color'] = $array['goods_color_alias'];$array['goods_sku'] = $array['goods_sku_alias'];return [//物料编码['title' => '物料编码', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[1]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys'  => $array['goods_sku']],], 'sleep' => 3],//供应商名称['title' => '供应商名称', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[5]/div[3]/div', 'option' => 'click']],['title' => '供应商名称', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[5]/div[3]/div/div/ul/li[3]', 'option' => 'click']],//物料描述['title' => '物料描述', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[6]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => $array['goods_title'] . '|' . $array['goods_color'],]],],//产品简称['title' => '产品简称', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[7]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => $array['goods_title'] . '|' . $array['goods_color']],],],//品牌['title' => '品牌', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[8]/div[3]/select', 'option' => 'select', 'type' => 'index', 'value' => (isset($array['brand']) ? $array['brand'] : 4),],],//产品品牌['title' => '产品品牌', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[9]/div[3]/select', 'option' => 'select', 'type' => 'index', 'value' => (isset($array['goods_brand']) ? $array['goods_brand'] : 4),],],//产品组['title' => '产品组', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[10]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => '其他',]],],//产品线['title' => '产品线', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[11]/div[3]/div', 'option' => 'click']],['title' => '产品线', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/div[11]/div[3]/div/div/ul/li[32]', 'option' => 'click']],//产品名称['title' => '产品名称', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[1]/div[1]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' =>  $array['goods_title'] . '|' . $array['goods_color']],],],//重量单位['title' => '重量单位', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[2]/div[1]/div[3]/select', 'option' => 'select', 'type' => 'index', 'value' => $array['weight_unit'],],],//计量单位['title' => '计量单位', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[2]/div[2]/div[3]/select', 'option' => 'select', 'type' => 'index', 'value' => $array['weight_unit']],],//最小计量单位数量['title' => '最小计量单位数量', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[2]/div[3]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => $array['weight']],],],//外包装尺寸(厚)['title' => '外包装尺寸(厚)', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[2]/div[4]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => $array['weight']],],],//外包装尺寸(高)['title' => '外包装尺寸(高)', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[2]/div[5]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => $array['weight']],],],//外包装尺寸(宽)['title' => '外包装尺寸(宽)', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[1]/fieldset[2]/div[6]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => $array['weight']],],],//切换tab OFC专用区['title' => '切换tab OFC专用区', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/ul/li[2]/a', 'option' => 'click',]],//货号['title' => '货号', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[2]/div[1]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' =>  $array['goods_code']]],],//条形码['title' => '条形码', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[2]/div[2]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' =>  $array['goods_code']]],],//税务编码['title' => '税务编码', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[2]/div[3]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' =>  $array['goods_code']]],],//堆放层数['title' => '堆放层数', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[2]/div[4]/div[3]/span/input', 'option' => ['clear' => 'clear', 'sendKeys' => '1',]],],//是否为代销['title' => '是否为代销', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[2]/div[5]/div[3]/div/div[1]/input', 'option' => 'click',]],//切换tab 其他['title' => '切换tab 其他', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/ul/li[12]/a', 'option' => 'click',]],//产品销售渠道['title' => '产品销售渠道', 'type' => 'findElement', 'do' => ['xpath' => '/html/body/form/div/div/div[12]/div[1]/div[3]/select', 'option' => 'select', 'type' => 'index', 'value' => 1,],],];}
}

总结:

其实就是将复杂冗长的代码逻辑通过数组来表达:

['type' => 'class', 'do' => ['className' =>  'app\yperp\model\Supplier', 'func' => 'status', 'args' => [$info['id'], 2]], 'sleep' => 3],

type:是指定使用什么操作,例如:执行一个类方法,做条件判断,查找一个元素,点击一个元素,上传文件等,

do:是执行这个操作的具体参数,每个操作需要的参数可能都不一样,

sleep:就是执行这个操作后需要等待的时间,有的情况下网络环境等其他原因需要等待页面加载或后台反馈后才进行下一步操作;

需要注意的是,文件上传需要确定是否需要删除原来的文件,并重新上传,例如电商图片等,所有上传的文件地址必须是以容器为主目录,所以需要将本地资源文件夹给到容器中,否则可能无法访问到文件资源;

相关文章:

  • 刷题第11天
  • 985硕的4家大厂实习与校招经历专题分享(part2)
  • 测试常用的Linux命令
  • 中医把脉笔记
  • react tab选项卡吸顶实现
  • 力资源视角的数字化应用
  • 01背包问题 刷题笔记
  • 排序算法:插入排序和希尔排序
  • 阿里云服务器怎么使用?3分钟搭建网站教程2024新版
  • 【设计模式】工厂模式与抽象工厂模式
  • SEO关键词策略:如何选取最适合你网站的关键词?
  • 一个简单的回调函数
  • IOS开发0基础入门UIkit-1cocoapod安装、更新和使用 , 安装中出现的错误及解决方案 M1或者M2安装cocoapods
  • javaSE-----继承和多态
  • 地平线零之曙光图文攻略,地平线零之曙光在MAC电脑能玩吗
  • 【Leetcode】104. 二叉树的最大深度
  • 【node学习】协程
  • 【翻译】Mashape是如何管理15000个API和微服务的(三)
  • DOM的那些事
  • ERLANG 网工修炼笔记 ---- UDP
  • Javascript 原型链
  • leetcode-27. Remove Element
  • LintCode 31. partitionArray 数组划分
  • MySQL几个简单SQL的优化
  • Solarized Scheme
  • 看域名解析域名安全对SEO的影响
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 前端代码风格自动化系列(二)之Commitlint
  • 前言-如何学习区块链
  • 我的面试准备过程--容器(更新中)
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 昨天1024程序员节,我故意写了个死循环~
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #LLM入门|Prompt#3.3_存储_Memory
  • #pragma pack(1)
  • #QT(TCP网络编程-服务端)
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (C#)一个最简单的链表类
  • (poj1.2.1)1970(筛选法模拟)
  • (二)斐波那契Fabonacci函数
  • (二十三)Flask之高频面试点
  • (二十一)devops持续集成开发——使用jenkins的Docker Pipeline插件完成docker项目的pipeline流水线发布
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (十一)图像的罗伯特梯度锐化
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .NET 使用 XPath 来读写 XML 文件
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .NET使用HttpClient以multipart/form-data形式post上传文件及其相关参数
  • /*在DataTable中更新、删除数据*/