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

在北京建设教育协会的网站彩页设计素材

在北京建设教育协会的网站,彩页设计素材,aso推广平台,红鱼洞水库建设管理局网站开头先让我碎碎念一波~去年差不多时间发布了一篇《 UiAutomator Nico#xff0c;一个基于纯 adb 命令实现的安卓自动化测试框》#xff08;https://testerhome.com/topics/37042#xff09;#xff0c; 由于种种原因 (详见此篇帖子) 当时选择了用纯 adb 命令来实现安卓自动…开头先让我碎碎念一波~去年差不多时间发布了一篇《 UiAutomator Nico一个基于纯 adb 命令实现的安卓自动化测试框》https://testerhome.com/topics/37042 由于种种原因 (详见此篇帖子) 当时选择了用纯 adb 命令来实现安卓自动化这个版本我且称为 Nico1.0 版本。 但在实际使用一段时候后确实遇到了各种问题首当其冲的就是 dump 元素慢即使强制不等待元素页面加载完成整体速度更是一言难尽。其次是获取元素树不够完整除了稳定似乎已经没有别的优点了。特别是在跑多条测试用例的时候时间成本是肉眼可见的直线上升。用 airtest 大概是 3 秒左右而如果用 adb dump 的方式则会来到 30 秒。 这时候一个问题摆在我面前是要选择切回到 Airtest 还是继续使用切回原来的测试方案这肯定不是我的行事风格既然选择了一条路走到黑那不管怎么样都得将眼前的难题攻克。 之前读过另一位大佬的系列文章 《【Day 0】关于我是如何干掉 Appium 和 RobotFramework 这件事的——开篇》https://vancheung.github.io/posts/Day-0-%E5%85%B3%E4%BA%8E%E6%88%91%E6%98%AF%E5%A6%82%E4%BD%95%E5%B9%B2%E6%8E%89Appium%E5%92%8CRobotFramework%E8%BF%99%E4%BB%B6%E4%BA%8B%E7%9A%84-%E5%BC%80%E7%AF%87/非常推荐大家去看完我相信对想要深耕自动化的同学会有很大帮助大佬的文章给了我很帮助在此隔空对这位大佬表示感谢有了大佬玉珠在前我便开始了我自己的对从零开始干掉 Appium 的攻略历程这个版本我称它为 Nico2.0。 接下来的文章将会从整体思路Android 部分, iOS 部分开始对技术原理关键代码讲解等方面进行深度剖析争取做到小白也能轻松理解。 知识预备 在介绍整个项目之前我先介绍一下目前所有移动端自动化操作实现的基本原理对自动化已有基本概念的同学可以跳过这段。无论是安卓还是 iOS当我们写脚本执行任意操作譬如查找元素操作元素的的时候其实真正在工作的是系统自带的自动化测试框架 UIAutomator 和 XCUITest。它们是安卓和 iOS 提供给开发者对自己的 App 应用进行 UI 自测的一个测试套件。即开发者可以在写单元测试自测的时候调用它们来做一些简单的 UI 界面的验证而它们底层其实都调用了各自的 Accessibility API 。 那么什么 Accessibility APIAccessibility API 是一组用于帮助开发人员创建可以被所有用户访问和使用的应用程序的接口和工具。它最初是为帮助有视力、听力或其他障碍的用户访问和使用计算机和移动设备而设计的。当我们打开手机中的辅助功能- 旁白之后我们点击屏幕的任意位置它都会出现一个白框并且发出语音告诉当前点击的是什么按钮。 为实现这个功能 Accessibility 则需要能够遍历整个用户界面获取其所有控件及其属性 (包括坐标id文本等)而这正是自动化测试所需要的能力。因此安卓和 iOS 使用这个接口提供了 UIAutomator 和 XCUITest。回到 UIAutomator 和 XCUITest 本身作为自动化测试套件对于开发者来说是非常友好的因为它们通常伴随着开发工具一同集成在 IDE 中编写脚本的语言也跟编写 app 的语言保持一致。但坏处是它无法脱离项目本身独立运行同时也要求测试人员要能够掌握对应的开发语言这明显是不现实的。 为了解决这一难题早期的大神们便想出了一个迂回策略借助一个中间商也就是在移动设备上安装一个独立的应用作为服务端通过这个应用实现调用系统自带的自动化测试套件的能力同时持续监听指定端口的接收到的请求然后再转而在设备上进行指定操作。 这里拿 appium 的工作流程图举例 以下是一段摘自 Chat GTP 的解析 各组件的功能与交互方式 Appium Server作为 HTTP 服务器监听客户端请求解析命令并与移动设备上的 Bootstrap 进程进行通信。 Bootstrap.jar在 Android 设备上运行的 Java 守护进程负责接收 Appium Server 的命令转发给 UIAutomator并返回执行结果。 UIAutomatorAndroid 系统自带的测试框架用于执行实际的自动化测试命令。 ADBAndroid Debug Bridge用于在 PC 与 Android 设备之间建立连接并允许用户发送各种命令到设备上。 客户端编写测试脚本的开发者使用的工具或库通过 HTTP 请求与 Appium Server 交互。 至此我们可以知道整个移动端自动化框架大致的运行原理之后我的 Nico 也是基于这个逻辑进行开发。 安卓方面 PC 端与移动端的通讯 基于之前的探索我放弃了使用 adb 命令来进行 dump 的操作还是转而使用和 appiumairtest 一样使用安卓自带的 UIAutomator2 来进行底层的元素 dump 和操作交互。 但我之前也有提到过最初不想使用这种这套模式的根本原因就是它存在一些弊端 (相对于我们的项目来说) 1.通讯过程比较复杂 2.在初始化 driver 时移动端的启动监听服务的 app 会在前台闪现一次 (设备重启的时候容易和我们自启动 app 冲突导致应用 crash) 首先是第一点在分析完整个 appium 的运行逻辑之后我认为整体流程其实可以简单理解为 PC 与 Mobile 之间进行通讯。那我们需要做的就是在移动端启动一个 server并持续监听一个指定端口例如8080然后我们可以借助端口转发工具 adb forward tcp 来进行端口发转这时候 PC 端只需要向 8080 端口发送请求就能够间接的访问到移动端。这里去掉的 appium server 的处理工作使用端口转发将 PC 端的请求直接转发给移动端 。逻辑如下图所示 然后是代码这里安卓服务点端的代码它启动了一个服务器线程该线程在指定端口上监听客户端连接通过解析传入的消息处理客户端请求并支持根据特定消息内容如 close关闭服务器连接同时主线程持续监控服务器线程的运行状态。 // 初始化服务器init();Thread serverThread new Thread(() - { try { // 获取传递给Instrumentation的参数 Bundle arguments InstrumentationRegistry.getArguments(); String portString arguments.getString(port); // 使用提供的端口如果没有提供则默认为9000 int port portString ! null ? Integer.parseInt(portString) : 9000; InetAddress serverAddress InetAddress.getByName(localhost); // 创建绑定到指定端口和地址的ServerSocket serverSocket new ServerSocket(port, 0, serverAddress); Log.i(TAG, 服务器正在监听: serverAddress.getHostAddress() : portString); while (true) { Log.i(TAG, 开始接收请求: ); // 接受一个传入的连接 Socket socket serverSocket.accept(); String msg; OutputStream outputStream; MapString, Object result; // 格式化传入的socket消息 result format_socket_msg(socket); msg (String) result.get(msg); outputStream (OutputStream) result.get(outputStream); // 确保消息和输出流不为空 assert msg ! null; assert outputStream ! null; // 检查消息是否包含close以关闭连接 if (msg.contains(close)) { socket.close(); // 关闭客户端socket serverSocket.close(); // 关闭服务器socket return; } // 处理客户端请求 handleClientRequest(socket, msg, outputStream); } } catch (IOException e) { e.printStackTrace(); }});// 启动服务器线程serverThread.start();// 主线程循环检查服务器线程是否仍在运行while (serverThread.isAlive()) { try { // 每秒检查一次 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }} 然后是 PC 端代码因为整体框架是 python 写的所以这里也是由 python 实现简单来说就是向指定端口发送 socket 的请求 def send_tcp_request(port: int, message: str): # logger.debug(fsend_tcp_request: {port} {message}) try: client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((localhost, port)) client_socket.sendall(message.encode()) client_socket.sendall(\n.encode()) # 接收服务器响应 chunks [] while True: chunk client_socket.recv(1024) # 一次最多接收 1024 字节数据 if not chunk: break chunks.append(chunk) client_socket.close() response b.join(chunks) if get_jpg_pic in message or message stop_recording or get_png_pic in message: return response else: response response.decode() return response except ConnectionRefusedError as b: logger.error(f{str(b)} by {port}) return f{str(b)} by {port} except ConnectionResetError as b: logger.error(f{str(b)} by {port}) return f{str(b)} by {port} 端口转发只需要执行adb forward tcp:xxxx tcp:xxxx这样就完成了一个完成的通讯过程。 接下来我们来说第二点问题。现有的大多数例如 Appium、Airtest 对服务端的实现基本都是依托一个常规的 apk 包在启动测试的时候调用adb shell am start -n com.example.package/.MainActivity这导致在启动 driver 的时候不可避免的会有一个启动页面然后才迅速进入后台的动作那么如何做到无感知地启动我们的测试 driver 呢经过一番调研我发现可以利用安卓测试包的特性来完成。 安卓的测试包Test APK是用于移动应用程序测试的安装包文件其全称是 Android Package Kit for Testing简称 APK for Testing。APK 是 Android 操作系统上的应用程序安装包格式而测试包则是在开发过程中用于进行各种测试如功能测试、性能测试、兼容性测试等的特定 APK 文件。 那么设计思路就是我写一个什么内容都没有的空包.apk然后再为其写一个测试包将所有调用 uiautomator 的逻辑以及服务监听的逻辑都放在里面。 接着使用adb shell am instrument -r -w -e port 指定端口 -e 空壳包包名 空壳测试包包名来启动监听服务当我看到如下的图的输出时表示 server 启动成功 然后让我发起 sokect 请求来查看是否能够成功读取数据到 dump 数据这里可以看到sockect 发送成功并且成功获取到了返回值。 到此为止就算是实现了整个通讯过程。 服务端的初始化和保活 与 Appium 不同的是Appium 通过会话来管理和与特定的设备交互每次启动一个新的测试时Appium 会创建一个新的会话并返回一个会话 ID。所有后续的命令都通过这个会话 ID 与 Appium 服务器通信以确保命令被发送到正确的设备和应用。 而 Nico 不存在额外的客户端因此管理与特定设备的交互都是通过指定端口号来实现的。且在 Appium 在初始化的过程中没有直接提供获取当前设备已启动的会话 id 的动作。这就意味着如下图我们在不同的测试脚本中分别初始化 driver会启动两个不同的会话移动端的 server 也会被启动两次。 当然我们也可以编写额外的逻辑来实现实例的共享避免不断产生新的会话。例如 pytest我们通常会在 setup 或者 conftest 中先初始化当前设备的实例然后在不同的测试脚本中去调用 但在 Nico 中我希望对 driver 的管理直接在底层处理掉简化整个框架的使用。因此我的初始化逻辑如下图 然后我们执行用例看看第一次完全初始化运行 从启动服务到执行查找总共花了 3s 接近 4s 第二次运行 由于不需要再次启动只花了 265ms 这样不仅能够极大的减少初始化的时间同时用户几乎不需要考虑实例共享的问题。每次直接实例化全新的类剩下的交由代码底层去判断。 再一个是保活的机制在我的印象里appium 是没有做保活机制的。即如果中途 UIAutomator Server crash 或者 appium 服务 crash整个会话则会中断且不会自动恢复。需要自己重启启动一个新的会话。 然后 Airtest 则是独立开了一个线程来持续观察移动端服务存活情况随时掉线随时重启。 这样做的好处就是可以保证移动端 UIAutomator Server 的健壮性但坏处就是如果 UIAutomator Server 重启的时间点刚好与当前 app 的某些操作重叠。比如 app 刚好也需要打开某个 Activity或者处于 Activity 切换的过程中UIAutomator Server 也正好在进行重启就会发生两个 app 互相抢占前台或者另一方启动失败。 我为了避免这种情况因此也没有做相关的保活措施。如同前文所说我会在每次调用请求接口之前先发送一个简单的状态查询。如果状态查询失败再重启 UIAutomator Server且因为我的 UIAutomator Server 是全程后台执行整个重启过程也不会让前台 app 有任何感知。 元素查找与操作 查找方面我借鉴了 Airtest 的语法规则添加了两种查找逻辑 1.nico(text蓝牙).wait_for_appearance(timeout) 在指定超时时间内不断重试超时则报错提示无法在指定时间内以指定查询条件找到该元素 2.nico(text蓝牙”).exit()在指定超时时间内不断重试超时只返回 rue, False 表示元素存在的状态不会报错 其他的一些都是基于这两类进行的延申详情可查看 github 里的 Readme这里就不赘述。值得一提的是除了基本的父子节点的获取之外我还添加了兄弟节点的直接获取例如nico(text蓝牙).last_sibling 和nico(text蓝牙).next_sibling 然后对于元素查找的底层逻辑我没有选择使用调用安卓 uiautomtor 自带的 API而是直接将当前整个元素页面 dump 下来获得一个 xml 结构的树。然后使用 lxml 库将其初始化为为一个lxml.tree的对象利用lxml.tree对象自带的查方式进行元素查找。这里的root参数即为我们 dump 下来的元素树对象query是一个查询条件的键值对例如{text:蓝牙}。 ###底层查 def __find_element_by_query_by_xml(root, query) - Union[list, None]: xpath_expression .//* conditions [] is_re False for attribute, value in query.items(): if attribute compressed: pass elif attribute xpath: xpath_expression convert_xpath(value) matching_elements root.xpath(xpath_expression) else: if attribute.find(_matches) 0: is_re True attribute attribute.replace(_matches, ) condition fre:match({attribute},{value}) elif attribute.find(_contains) 0: attribute attribute.replace(_contains, ) condition fcontains({attribute},{value}) else: condition f{attribute}{value} conditions.append(condition) if conditions: xpath_expression [ and .join(conditions) ] if is_re: ns {re: http://exslt.org/regular-expressions} matching_elements root.xpath(xpath_expression, namespacesns) else: matching_elements root.xpath(xpath_expression) if len(matching_elements) 0: return None else: return matching_elements 这意味着移动端只负责 dump 元素所有查询逻辑全部放到 pc 端来处理。那么这样做的有什么好处呢听我娓娓道来。 首先也是最重要的一点这个 dump 元素的速并不算慢而且dumpWindowHierarchy 这个方法还有一个参数是 compressed用于控制是否需压缩元素。代价是失去一些不重要的元素节点但却能够进一步提升元素 dump 的速度 (如果有特殊需要也可以将参数设为 false 以获取更完整的元素树)。 private void handleDumpRequest(OutputStream outputStream,boolean compressed) throws IOException { File response dumpWindowHierarchy(compressed, ui_tree.xml); try (FileInputStream fis new FileInputStream(response)) { byte[] buffer new byte[1024]; int length; while ((length fis.read(buffer)) ! -1) { outputStream.write(buffer, 0, length); } outputStream.flush(); // 确保所有的数据都被写入到OutputStream } catch (IOException e) { e.printStackTrace(); } } 在 dump 不算慢的基础上我们能够获取整个元素结构和其所有属性这样非常便于我们去获取当前元素的兄弟节点父子节点。要知道有时候当一个元素不存在唯一标识符的时候相对位置的元素查找方式是一个非常好用的这里吐槽一下airtest 中没有直接通过当前元素节点获取上下节点的方法提了 PR , 过了快两年了也没人审核通过。同时一次 dump 即可获取左右兄弟或者父子各种节点无需发送多次请求。 然后为了进一步减少请求次数我设计了缓存机制每一次 dump 元素都将当前的元素树写入临时环境变量进行缓存。 并且通过一个全局的 tag 来记录当前 UI 页面是否变化。在执行 UI 查找动作前先获取当前 tag 的状态为True则进行新一轮的 dump反之则直接使用缓存的元素数据。 def _get_root_node(self, configuration: dict): get the root node of the element tree param configuration: The configuration of the platform param udid: The device ID param port: The port number of automation server param force_reload: Whether to force reload the element tree, default is False return: The root node of the element tree ui_change_status RunningCache(self.udid).get_ui_change_status() # logger.debug(fui tree change is {ui_change_status}) if not ui_change_status: # logger.debug(f{self.udid}s UI no change. There is no need to dump again!!) return RunningCache(self.udid).get_current_cache_ui_tree() else: # logger.debug(f{self.udid}s UI is change. dump again!!) return self._dump_ui_xml(configuration) 这样在进行一个静态页面的多元素检查时可以减少请求次数提升测试速度。例如以下场景需要同时检查多个文本是否存在实际上只需要在发起一次 dump 请求后续的几次查找都是基于之前的缓存内容。 如果此时执行了一些动作例如元素点击滑动等都会在执行之后主动清除缓存元素树。 def click(self, xNone, yNone, x_offsetNone, y_offsetNone): if x is None and y is None: x self.center_coordinate()[0] y self.center_coordinate()[1] if x_offset is not None: x x x_offset if y_offset is not None: y y y_offset command fadb -s {self.udid} shell input tap {x} {y} os.system(command) RunningCache(self.udid).clear_current_cache_ui_tree() logger.debug(fclick {x} {y}) 同时在移动端我额外添加了一个监听者线程用于检查页面 UI 是否发生变动这样针对一些动态场景一样能保证及时的刷新 private final UiAutomation.AccessibilityEventFilter checkWindowUpdate event - { if (event.getEventType() AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) { lastWindowChangeEvent event; return true; } return false;};private final AtomicBoolean uiChanged new AtomicBoolean(false);private void startWatchingUiChanges() { Thread watcherThread new Thread(() - { while (true) { try { InstrumentationRegistry.getInstrumentation().getUiAutomation().executeAndWaitForEvent( () - {}, checkWindowUpdate, 5000 ); uiChanged.set(true); } catch (TimeoutException e) { continue; } } }); watcherThread.start();} 对于元素的操作我还是选择在获取到元素之后通过该元素的坐标直接调用 adb shell 命令来直接进行 click 等 def click(self, xNone, yNone, x_offsetNone, y_offsetNone): if x is None and y is None: x self.center_coordinate()[0] y self.center_coordinate()[1] if x_offset is not None: x x x_offset if y_offset is not None: y y y_offset command fadb -s {self.udid} shell input tap {x}{y} os.system(command) RunningCache(self.udid).clear_current_cache_ui_tree() logger.debug(fclick {x}{y}) 为啥不调 uiautomator 的 api 呢一个原因是我想减少与 uiautomator 服务的请求次数第二就是我偷懒了:想少写点安卓的代码。。。再加上目前 adb 命令基本可以覆盖大部分场景了如果有额外的需求再往 apk 里加吧毕竟底层也是自己写的。 结束语 以上就是 Nico 安卓部分全部内容了由于篇幅有点长了。。剩下的内容我将分成另外的篇幅进行撰写。接下来我会带来 iOS 部分以及一个额外的 inspector(检索 iOS 和安卓页面元素的工具的分享同样会包括技术原理和关键代码讲解。
文章转载自:
http://www.morning.wngpq.cn.gov.cn.wngpq.cn
http://www.morning.ryfq.cn.gov.cn.ryfq.cn
http://www.morning.qmnhw.cn.gov.cn.qmnhw.cn
http://www.morning.mhlsx.cn.gov.cn.mhlsx.cn
http://www.morning.kstgt.cn.gov.cn.kstgt.cn
http://www.morning.xkhhy.cn.gov.cn.xkhhy.cn
http://www.morning.tgnwt.cn.gov.cn.tgnwt.cn
http://www.morning.lpbrp.cn.gov.cn.lpbrp.cn
http://www.morning.lnrhk.cn.gov.cn.lnrhk.cn
http://www.morning.yhglt.cn.gov.cn.yhglt.cn
http://www.morning.krjyq.cn.gov.cn.krjyq.cn
http://www.morning.zxrtt.cn.gov.cn.zxrtt.cn
http://www.morning.pmjw.cn.gov.cn.pmjw.cn
http://www.morning.21r000.cn.gov.cn.21r000.cn
http://www.morning.pmdzd.cn.gov.cn.pmdzd.cn
http://www.morning.zjqwr.cn.gov.cn.zjqwr.cn
http://www.morning.rrcxs.cn.gov.cn.rrcxs.cn
http://www.morning.rtspr.cn.gov.cn.rtspr.cn
http://www.morning.sqqds.cn.gov.cn.sqqds.cn
http://www.morning.ujianji.com.gov.cn.ujianji.com
http://www.morning.cpmwg.cn.gov.cn.cpmwg.cn
http://www.morning.bpmdz.cn.gov.cn.bpmdz.cn
http://www.morning.lbqt.cn.gov.cn.lbqt.cn
http://www.morning.jnbsx.cn.gov.cn.jnbsx.cn
http://www.morning.ykmg.cn.gov.cn.ykmg.cn
http://www.morning.bgygx.cn.gov.cn.bgygx.cn
http://www.morning.kjjbz.cn.gov.cn.kjjbz.cn
http://www.morning.mnnxt.cn.gov.cn.mnnxt.cn
http://www.morning.sgbk.cn.gov.cn.sgbk.cn
http://www.morning.lgphx.cn.gov.cn.lgphx.cn
http://www.morning.wzdjl.cn.gov.cn.wzdjl.cn
http://www.morning.trsfm.cn.gov.cn.trsfm.cn
http://www.morning.lpzqd.cn.gov.cn.lpzqd.cn
http://www.morning.yrbqy.cn.gov.cn.yrbqy.cn
http://www.morning.rbnp.cn.gov.cn.rbnp.cn
http://www.morning.srltq.cn.gov.cn.srltq.cn
http://www.morning.mjkqj.cn.gov.cn.mjkqj.cn
http://www.morning.flfxb.cn.gov.cn.flfxb.cn
http://www.morning.sjsfw.cn.gov.cn.sjsfw.cn
http://www.morning.rfmzs.cn.gov.cn.rfmzs.cn
http://www.morning.gbjxj.cn.gov.cn.gbjxj.cn
http://www.morning.cjqcx.cn.gov.cn.cjqcx.cn
http://www.morning.hjjfp.cn.gov.cn.hjjfp.cn
http://www.morning.rcyrm.cn.gov.cn.rcyrm.cn
http://www.morning.nydtt.cn.gov.cn.nydtt.cn
http://www.morning.qgtbx.cn.gov.cn.qgtbx.cn
http://www.morning.smdiaosu.com.gov.cn.smdiaosu.com
http://www.morning.ydgzj.cn.gov.cn.ydgzj.cn
http://www.morning.byzpl.cn.gov.cn.byzpl.cn
http://www.morning.gsjzs.cn.gov.cn.gsjzs.cn
http://www.morning.ayftwl.cn.gov.cn.ayftwl.cn
http://www.morning.bnrnb.cn.gov.cn.bnrnb.cn
http://www.morning.mcndn.cn.gov.cn.mcndn.cn
http://www.morning.knlyl.cn.gov.cn.knlyl.cn
http://www.morning.qcbhb.cn.gov.cn.qcbhb.cn
http://www.morning.pwmm.cn.gov.cn.pwmm.cn
http://www.morning.mnbgx.cn.gov.cn.mnbgx.cn
http://www.morning.tpchy.cn.gov.cn.tpchy.cn
http://www.morning.nyqm.cn.gov.cn.nyqm.cn
http://www.morning.rnpt.cn.gov.cn.rnpt.cn
http://www.morning.smnxr.cn.gov.cn.smnxr.cn
http://www.morning.ksjnl.cn.gov.cn.ksjnl.cn
http://www.morning.hryhq.cn.gov.cn.hryhq.cn
http://www.morning.jtfsd.cn.gov.cn.jtfsd.cn
http://www.morning.mqbdb.cn.gov.cn.mqbdb.cn
http://www.morning.phgz.cn.gov.cn.phgz.cn
http://www.morning.tgczj.cn.gov.cn.tgczj.cn
http://www.morning.cgntj.cn.gov.cn.cgntj.cn
http://www.morning.chxsn.cn.gov.cn.chxsn.cn
http://www.morning.lfdrq.cn.gov.cn.lfdrq.cn
http://www.morning.cfcdr.cn.gov.cn.cfcdr.cn
http://www.morning.zwgrf.cn.gov.cn.zwgrf.cn
http://www.morning.ctqbc.cn.gov.cn.ctqbc.cn
http://www.morning.zkqjz.cn.gov.cn.zkqjz.cn
http://www.morning.rqhdt.cn.gov.cn.rqhdt.cn
http://www.morning.pgrsf.cn.gov.cn.pgrsf.cn
http://www.morning.frpb.cn.gov.cn.frpb.cn
http://www.morning.xphls.cn.gov.cn.xphls.cn
http://www.morning.lxhny.cn.gov.cn.lxhny.cn
http://www.morning.skbbt.cn.gov.cn.skbbt.cn
http://www.tj-hxxt.cn/news/255537.html

相关文章:

  • 不同用户入口的网站样板网页设计公司如何看待极简风格
  • 杭州网站外包网页游戏开服表最新
  • 网络科技公司网站源码下载什么行业必须做网站
  • 网站建设维护培训会上的讲话wordpress编辑文章图片文字对齐
  • 蚌埠网站建设公司福州市做网站公司
  • 帮公司做网站赚钱吗大型网站 jquery
  • 网站icp备案查询合肥seo推广排名
  • 山东青岛网站建设公司免费自助网站建站
  • 南京便宜网站建设万网 x3 wordpress
  • 网站排名优化是怎么做的wordpress配置是提升数据库错误
  • 广州网站优化专家wordpress 精致主题
  • o2o网站建设流程恢复wordpress修订版本
  • 外贸公司英文网站建设电脑网页翻译
  • c#网站开发网易云课堂百度云下载网站建设和网站设计有什么区别
  • 制作网站的布局网络广州越秀区发布
  • wordpress网盘搜索引擎衡阳网站优化公司
  • 织梦怎么制作手机网站湖北 商城网站建设多少钱
  • 上海知名网站建设公司排名数据分析师报名官网
  • 网站建设策划方案怎么写象山县建设工程招投标网站
  • 做网站前台模型要做什么呢贵阳网站建设多钱钱
  • c 网站开发类似优酷做动漫网站用什么程序
  • 烟台百度网站推广门户网站建设 报价
  • 天津网站页面设计长沙做网站公司
  • 网站设计模板下载网站建设下什么科目
  • 网站服务器租用开票应该网站滑动
  • 乐都企业网站建设公司建设牌安全带官方网站
  • 手机app制作pdf天津外贸seo推广
  • 山东省建设厅教育网站做网站怎么给图片加连接
  • 公司网站开发制作学校网站建设的好处
  • 公共网站怎地做网页制作培训班课程