最近需要批量操作某个古老的后台,但是流程非常复杂,通过 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-20231129
container_name: chrome
hostname: yp
privileged: true
shm_size: 4g
ports:
- 4443:4444
- 5901:5900
- 7902:7900
volumes:
- ./public/uploads/goods:/home/seluser/Downloads
networks:
- net
edge:
image: selenium/standalone-edge:4.15.0-20231129
container_name: edge
hostname: yp
privileged: true
shm_size: 4g
ports:
- 4441:4444
- 5902:5900
- 7901:7900
volumes:
- ./public/uploads/goods:/home/seluser/Downloads
networks:
- net
ocrapi:
container_name: ocrapi
build:
context: ./docker
dockerfile: Dockerfile
volumes:
- ./docker/ocr.py:/ocrapi/ocr.py
ports:
- "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 ddddocr
app = 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)}), 400
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
以上是环境搭建,ocr按目录载入即可,docker自动构建部署;
ocr是基于容器实现的,通过接口的方式调用,也可以独立使用;
接下来就是核心工具trait ,需要创建一个自定义命令,然后use这个trait
<?php
namespace 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来管理容器的重启的
<?php
declare(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;
}
}
命令行实例:
<?php
namespace 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();
}
}
下面是生成工作流的数组实例,可按需使用,按顺序推送到队列,即可按操作执行;
<?php
declare(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:就是执行这个操作后需要等待的时间,有的情况下网络环境等其他原因需要等待页面加载或后台反馈后才进行下一步操作;
需要注意的是,文件上传需要确定是否需要删除原来的文件,并重新上传,例如电商图片等,所有上传的文件地址必须是以容器为主目录,所以需要将本地资源文件夹给到容器中,否则可能无法访问到文件资源;