Appium - 入门指南(偏重于实际操作)
前言
此文章 偏向于实操
appium支持在不同平台上(windows,linux、mac) - 使用测试脚本(支持不同语言C #,json,Ruby等等) - 模拟测试各个平台的app(web、windows、mobile等)运行情况,支持ios和安卓平台上的原生应用,web应用和混合运用
appium类库封装了标准Selenium客户端类库,为用户提供所有常见的JSON格式selenium命令以及额外的移动设备控制相关的命令,如多点触控手势和屏幕朝向。
一、 Appium介绍
官网:http://appium.io/docs/cn/about-appium/intro/
这里主要是windows平台下的安装和使用。
1.1 设计理念
1.1.1 使用系统自带的自动化框架
使用各个系统自带的自动化框架可以让appium 独立运行
- 不需要把 Appium 特定的或者第三方的代码编译进应用
- 不用为了自动化而重新编译或修改应用
- 各个系统使用的自动化框架:
iOS 9.3 及以上: 苹果的 XCUITest
iOS 9.3 及以下: 苹果的 UIAutomation
Android 4.3+: 谷歌的 UiAutomator / UiAutomator2
Android 2.3+: 谷歌的 Instrumentation. (通过绑定独立的项目—— Selendroid 提供对 Instrumentation 的支持)
Windows: 微软的 WinAppDriver
1.1.2 支持多种语言 --WebDriver API
把各种系统本身提供的框架包装进一套 API —— WebDriver API 中
WebDriver(也叫「Selenium WebDriver」)。
在WebDriverAPI中,规定了一个客户端-服务器协议(称为 JSON Wire Protocol),按照这种客户端-服务器架构,可以使用任何语言编写的客户端向服务器发送适当的 HTTP 请求。
Appium & WebDriver 客户端在技术上而言不是「测试框架」,而是「自动化程序库」
1.2 架构
1.2.1 客户端 / 服务器架构
Appium 的核心一个是暴露 REST API 的 WEB 服务器。它接受来自客户端的连接,监听命令并在移动设备上执行,答复 HTTP 响应来描述执行结果
1.2.2 Appium服务器
Appium 是一个用 Node.js 写的服务器。可以从源码构建安装或者从 NPM 直接安装:
$ npm install -g appium
$ appium
- 它监听符合API规范,从WebDriver 传入的HTTP request。
- WebDriver是一个W3C规范,Appium团队为该协议添加了一些扩展,使其适用于web浏览器,同时也适用在任何设备上测试的任何应用程序。
- Appium Server服务器需要在Node.js web服务器中运行。
- Node.js web服务器可以在运行测试之前在本地机器上启动,也可以托管Appium服务器-让它作为运行测试的一部分。
1.2.3 Appium 客户端
Appium 客户端,不同客户端支持不同语言,来发起http命令。比如postman,Appium server gui等等
- 让Appium客户端与Appium Server服务器交互
- 解读WebDriver协议,将WebDriver协议转换为我们可以轻松使用的客户端库。
Appium提供了相应客户端的库。比如C# 有NuGet包。
1.2.4 会话(Session)
自动化始终在一个会话的上下文中执行。
通过客户端程序库以各自的方式发起与服务器的会话,但最终都会发给服务器一个 POST /session 请求,请求中包含一个被称作「预期能力(Desired Capabilities)」的 JSON 对象。这时服务器就会开启这个自动化会话,并返回一个用于发送后续命令的会话 ID。
1.2.5 预期能力(Desired Capabilities)
预期能力(Desired Capabilities)是一些发送给 Appium 服务器的键值对集合(比如 map 或 hash),它告诉服务器我们想要启动什么类型的自动化会话。
也有许多能力(Capabilities)可以修改服务器在自动化过程中行为。例如,我们可以将 platformName 能力设置为 iOS,以告诉 Appium 我们想要 iOS 中的应用会话,而不是 Android 或者 Windows 应用会话
二、环境配置
安装 npm添加链接描述, https://nodejs.org/en/
安装Appium:Appium 有命令行版本和桌面版本。可以两个都装上,桌面版本:在appium官网下载并安装Appium。命令行版本,管理员模式运行cmd:
npm install -g appium
可能需要设置网络代理。安装 node.js, https://nodejs.org/en/download/
安装 UIRecorder ,https://github.com/Microsoft/WinAppDriver/releases
安装结束后,在cmd界面输入“appium-doctor”检查是否缺少东西。
电脑启用“开发者模式”
添加环境变量
安装各平台应用的驱动程序,通过驱动程序,Appium server才能将我们的测试命令翻译给Appium客户端以及应用
- WinAppDriver - Windows平台的驱动程序。针对windows应用自动化 WinAppDriver
- UiAutomator2 - Android,它是Android SDK 安装包的一部分,安装 Java and Android SDK后就可以运行了
- XCUITest - Mac,ios 研发需要Xcode,Xcode只适用于Mac
- 安装Appium inspector。用来查看、定位界面元素。如果使用有问题,可以去github上搜索:https://github.com/appium/appium-desktop/issues
Appium Inspector 有两种版本,
桌面app,下载链接https://github.com/appium/appium-inspector/releases
- Appium 1.22版本之前都附带了Inspector 工具,入口在打开之后的搜索图标里(如下图),新版的inspector已经和Appium server不在一起了,需要单独安装。
web应用,由Appium Pro托管。直接打开网页就可以 https://inspector.appiumpro.com/
这两个应用程序有完全相同的功能,开网页版会更容易,并节省磁盘空间,同时可以保持多个标签打开!
需要注意的是Appium Inspector设计为默认使用Appium 2.0 (W3C WebDriver协议)。因此,如果是从Appium Desktop(默认情况下设计为使用Appium 1.x)迁移,可能会有一些不同的地方
appium-doctor提示问题以及解决办法:
2.1ANDROID_HOME is not set
解决办法
- 安装过adb的可以在系统变量-path中找到adb的安装路径(因为我用自己安装的路径,缺少android.bat文件,所以我这里解决办法是采用adb路径是安装vs2022时顺带安装的-勾选移动应用开发,包含AndroidSDK项)
- 电脑-高级系统设置-系统变量-新增变量ANDROID_HOME ,值设置为adb 文件夹的路径。
- adb完整路径是C:\Microsoft\AndroidSDK\25\platform-tools,ANDROID_HOME路径设置为C:\Microsoft\AndroidSDK\25
2.2 JAVA_HOME is not set
解决办法:
- 在系统变量中找到找到JDK的安装路径
- 新建变量JAVA_HOME,JDK的路径。这里直接复制完整路径即可。
成功解决之后再次测试appium-doctor,提示全部ok
✔ ANDROID_HOME is set to "C:\Microsoft\AndroidSDK\25"
✔ JAVA_HOME is set to "C:\Program Files\Microsoft\jdk-11.0.12.7-hotspot\bin."
✔ ADB exists at C:\Microsoft\AndroidSDK\25\platform-tools\adb.exe
✔ Android exists at C:\Microsoft\AndroidSDK\25\tools\android.bat
✔ Emulator exists at C:\Microsoft\AndroidSDK\25\tools\emulator.exe
✔ Android Checks were successful.✔ All Checks were successful
(node:25404) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
三、Appium - 测试程序详细解析
一个简单的自动化程序主要分为启动、交互。
不管哪个客户端,启动的步骤和所需要的参数都是一样的。
启动前要设置初始化参数,告知服务器启动哪一个应用app,在哪个平台,用哪个版本
交互要确认交互的对象 - 哪一个ui元素,交互的动作,以及如何判断动作结束
3.1 Appium 服务器初始化参数(Capability)
AppiumOptions是Appium中一个关键类
在启动应用时可以设置这些类中的这些参数 http://appium.io/docs/cn/writing-running-appium/caps/#appium-capability
3.1.1 Windows应用设置参数
keyName | 描述 | Value |
---|---|---|
appiumOptions.App | 应用 | exe本地绝对路径_或_应用id |
appiumOptions.PlatformName | 操作系统 | “windows”; |
appiumOptions.DeviceName | 设备类型 | “windowsPC”; |
上面这三个是Windows应用必须要设置的
3.1.2 Android应用设置参数
keyName | 描述 | Value |
---|---|---|
platformName | 使用的手机操作系统 | iOS, Android等 |
platformVersion | 手机操作系统的版本 | 例如 7.1, 4.4 |
deviceName | 使用的手机或模拟器类型 |
adb devices 命令查看当前设备名称。在 Andorid 上虽然这个参数目前已被忽略,但仍然需要添加上该参数。 为确保在连接多个设备时连接到正确的 Android 设备,请使用 udid 功能。
|
appActivity | 指从你的包中所要启动的 Android acticity。他通常需要在前面添加. (例如 使用 .MainActivity 代替 MainActivity) | MainActivity, .Settings |
appPackage | 运行的 Android 应用的包名 | com.example.android.myApp, com.android.settings |
app | 本地绝对路径_或_远程 http URL 所指向的一个安装包(.ipa,.apk,或 .zip 文件)。Appium 将其安装到合适的设备上。请注意,如果您指定了 appPackage 和 appActivity 参数(见下文),Android 则不需要此参数了。该参数也与 browserName 不兼容。 | /abs/path/to/my.apk 或 http://myapp.com/app.ipa |
3.2 UI元素定位与交互
如果是WPF程序,在该控件的Automation里面可以找到对应的设置,比如Name,AccessibilityID。
在inspector 里面,可以看到界面元素具体的值
http://appium.io/docs/en/commands/element/find-elements/index.html#selector-strategies
method | 描述 | Value |
---|---|---|
ID | Native element identifier. resource-id for android; name for iOS. | |
AccessibilityID | AutomationID | |
Name | 组件的名称 | |
class Name | ui组件的类型 | BUTTON,MENUBAR等等 |
xpath | 路径定位,依赖元素绝对路径或相关属性定位。不太推荐这种方式 |
四、Appium实战 之启动应用
4.1 通过Inspector启动 - 记事本应用
Appium inspector是一个软件,通过他可以查看界面上的所有元素信息,比如某个按键的名字、控件类型、xy坐标,等等
通过简单设置Appium inspector中的参数,快速启动应用,一方面为后面定位控件元素做好准备,一方面可以通过启动应用所需要的参数,参考到自动化程序中。
管理员模式启动
Appium server
,
可以管理员启动桌面版Appium Server GUI.exe,输入Host127.0.0.1和端口4723。
也可以通过管理员模式启动cmd,输入appium
,启动命令行版本
因为inspector(或者其他客户端)与 应用(或者模拟器)之间的通讯交互都需要通过Appium server来传达与翻译。所以要先启动Appium server。
后续交互历史记录在Appium server界面也可以看到详细信息。
管理员模式启动
Appium Inspector
,设置Remote path
如果Appium版本是1.x 而inspector 版本是新的,就需要设置远端路径 - Remote path 为/wd/hub
如果Appium 版本是2.x 则不用设置
Appium版本为 v1.22.3,所以需要设置
- wd 可以理解是WebDriver 的缩写
- hub 是指主(中心) 节点,在selenium 分布式里中心节点
![在这里插入图片描述](https://img-blog.csdnimg.cn/edec3033d4b54cfe8313ae5481ceafe2.png
- 添加
Capabilities
在Desired Capabilities 下面,依次添加需要启动应用的信息。
app:要启动的应用,这里添加notepad.exe的路径
deviceName:启动的设备
platformName:这个应用所需要使用的平台
这三个信息是必要的。告诉Appium inspector我们需要在何种环境下启动何种应用
右边会自动将我们填入的信息转换成JSON 表达方式
点击save,保存现在的信息。 - 点击
Start Session
启动session后,inspector会启动记事本应用,
- 左侧界面预览区,是和当前应用同步界面的预览画面,不会实时更新。需要手动点击刷新按钮来同步画面。但是在预览区点击按钮,实际打开的应用会实时同步操作。
- App Source,查看当前界面元素信息,这些信息可以用来帮助我们在写自动化时定位某个界面元素。
五、Appium实战 之编写Mstest 自动化程序
自动化测试程序是模拟应用启动、运行,以及其他的一些特定操作。
所以基于inpector启动应用的流程,我们可以在自己加一些操作,比如输入字符串,保存文档等。
我这里是对自己写的WPF应用进行自动化测试,先在文本框中输入字符串,然后保存文档。
5.1 新建MSTest 测试项目
,或者单元测试项目
5.2 添加NuGet包
项目上右键 - NuGet包管理 - 搜索并添加NuGet包,需要添加以下三个:
- Microsoft.WinAppDriver.Appium.WebDriver
- Selenium.WebDriver
- Selenium.Support
在安装时,注意看下面的依赖项 版本说明,比如.Appium.WebDriver,需要Selenium 包相关版本为3.8.0+。工具-程序包管理控制台, 输入Get-Package
获取当前项目的NuGet包信息
我安装的版本信息如下,
- Appium.WebDriver {5.0.0-alpha} NodepadTest
- Selenium.WebDriver {4.0.0} NodepadTest - Selenium.Support {4.0.0} NodepadTest
官网Samples项目版本如下 (Appium官方样例代码下载地址:https://github.com/Microsoft/WinAppDriver/tree/v1.0#using-appium
)
- Microsoft.WinAppDriver.Appium.We… {1.0.1-Preview} NotepadCalculatorTest
- Selenium.Support {3.8.0} NotepadCalculatorTest
- Selenium.WebDriver {3.8.0} NotepadCalculatorTest
Appium版本更新的时候,会优化很多功能,如果报一些错误,可以试试把版本改一改。
- Microsoft.WinAppDriver.Appium.WebDriver 这是一个用于Appium,临时的Selenium WebDriver的扩展,它实现了Windows应用程序驱动程序(WinAppDriver)的操作API功能。这仅用于预览目的。 Microsoft WinAppDriver 提供
- Appium.WebDriver - Selenium Webdriver extension for Appium. 由Appium提供
https://www.nuget.org/packages 可以搜索关于NuGet包的相关信息
这两个包选择一个使用,我这里选择的- Appium.WebDriver {5.0.0-alpha} NodepadTest
5.3 添加session
- 添加using 引用库
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Service;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium;
using System;
using System.Threading;
using OpenQA.Selenium.Appium.Interactions;
using OpenQA.Selenium.Interactions;
using System.Collections.Generic;
using OpenQA.Selenium.Support.UI;
- 添加session 启动function,在method下调用这个函数的话,就可以启动该Application了。我这里用的是我自己写的一个WPF应用。可以用notepad.exe
public WindowsDriver<WindowsElement> StartNodepadApplication(){//1.add capabilitiesvar appiumOptions = new AppiumOptions();//appiumOptions.App = @"C:\Windows\System32\notepad.exe";//notepadappiumOptions.App = "D:\\test\\Appium\\WPFApplication\\AppiumMyApp\\AppiumMyApp\\bin\\x64\\Release\\net6.0-windows\\AppiumMyApp.exe"; appiumOptions.PlatformName = "windows";appiumOptions.DeviceName = "windowsPC";//2.start the drivervar appiumLocalServer=new AppiumServiceBuilder().UsingAnyFreePort().Build();appiumLocalServer.Start();var driver = new WindowsDriver<WindowsElement>(appiumLocalServer, appiumOptions);//return the sever sessionreturn driver;}
- 完善TestMethod,定位界面元素,并进行交互操作
- 点击clear button
- 点击编辑button
- 发送字符串到文本框
- 点击save button
- 建立DefaultWait,每500ms轮询一次,查看是否弹出了保存文件的对话框,我这里用来界面出现保存button ,如果出现表示前面的步骤执行结束。才会继续执行后续的动作
- 同样建立第二个DefaultWait,等到界面弹出一个对话框 ,包含一个元素Name为“文件已导出”,表示文件已经保存完毕了。
[TestMethod]public void TestWaitForProgressBar(){//start sessionvar session = StartNodepadApplication();// FindUIElementMethod1 -Name,click clear buttonvar clearButton = session.FindElementByName("Clear");clearButton.Click();// FindUIElementMethod2 -AccessibilityId, click buttonvar textBox = session.FindElementByAccessibilityId("MyTextBoxID");textBox.Clear();//SendKeys textBox.SendKeys("-----------------------------------------\n");textBox.SendKeys("******************************************\n");textBox.SendKeys("my name is jerry\n");//click savevar saveButton= session.FindElementByName("Save");saveButton.Click();//Wait for the progress to process for a while.untill Element appear or disappear//1.add a DefaultWait,Poll once every 500msvar wait = new DefaultWait<WindowsDriver<WindowsElement>>(session){Timeout = TimeSpan.FromSeconds(60),PollingInterval = TimeSpan.FromMilliseconds(500)};wait.IgnoreExceptionTypes(typeof(NoSuchElementException));//界面能找到,说明前面动作已经执行完毕wait.Until(d => d.FindElementByName("保存(S)"));//click save button in SaveFileDialogvar dialogSavebutton = session.FindElementByName("保存(S)");dialogSavebutton.Click();//2.var wait2 = new DefaultWait<WindowsDriver<WindowsElement>>(session){Timeout = TimeSpan.FromSeconds(60),PollingInterval = TimeSpan.FromMilliseconds(500)};wait.IgnoreExceptionTypes(typeof(NoSuchElementException));//find element to verify save donewait.Until(d =>{return d.FindElementByName("文件已导出");});session.FindElementByName("确定").Click();textBox = session.FindElementByAccessibilityId("MyTextBoxID");textBox.Clear();textBox.SendKeys("file save done this is second time I am here-\n");session.Close();}
项目右键-运行测试-可以看到会开始执行自动化测试。
六、编写android自动化应用测试程序
6.1 inspector 连接真机
6.1.1获取连接真机app的相关参数
准备工作:手机开启开发者模式
,(一般连续点击系统版本5下),开启usb 调试模式
最好是能使用root 命令的手机,因为我在使用普通手机时,出现了连接不上的问题
第三章提到过,安卓应用启动需要设置以下Capabilities,获取方式标注在了右边
可参考链接:https://blog.csdn.net/u012028250/article/details/120371345
keyName | 描述 | adb 命令 |
---|---|---|
platformName | 手机操作系统 | Android |
platformVersion | 操作系统版本 |
adb shell getprop ro.build.version.release
|
deviceName | 手机或模拟器类型 |
adb devices |
appPackage | 运行的 Android 应用的包名 |
手机上打开要测试的app,输入 adb shell "dumpsys window | grep mCurrentFocus"
|
appActivity | 从包中所要启动的 Android acticity | 同上 |
appPackage获取方式有如下几种:package都是com.xxx
adb shell pm list packages
列出手机上所有的包,找到所需要的包名adb shell am monitor
获取当前应用程序包名
appActivity获取方式:
- adb shell dumpsys activity | findstr “mResume”
com.sec.android.app.camera/.Camera t243} ---即appPackage / appActivity
adb shell "dumpsys window \| grep mCurrentFocus"
mCurrentFocus=Window{c22cf8f u0 com.sec.android.app.camera/com.sec.android.app.camera.Camera} ---即appPackage / appActivity
获取之后填入到inspector中
图片中SM-A336B
是通过adb shell getprop ro.product.model
获取的,指的是手机型号,可填可不填。
appium:automationName:指以什么方式解析界面元素UiAutomator2
填完之后,点击start session,手机会自动安装appium 相关软件,然后就可以启动手机中应用,并且获取界面控件元素了。
6.1.1查看界面元素信息
6.2 MSTest测试程序连接真机
6.2.1 参考inspector启动应用
private AndroidDriver<AndroidElement> StartApp(){System.Environment.SetEnvironmentVariable("ANDROID_HOME", @"C:\\Microsoft\\AndroidSDK\\25");System.Environment.SetEnvironmentVariable("JAVA_HOME", @"C:\\Program Files\\Microsoft\\jdk-11.0.12.7-hotspot");//connect to a device or emulatorvar capabilities = new AppiumOptions();capabilities.DeviceName=@"SM-A336B-58f407b9db347ece";//capabilities.DeviceName = @"58f407b9db347ece";capabilities.AutomationName = @"UiAutomator2";capabilities.PlatformName = "Android";capabilities.PlatformVersion = "12";//specifying which app we want to install or launchcapabilities.AddAdditionalAppiumOption("appPackage", "com.sec.android.app.camera");capabilities.AddAdditionalAppiumOption("appActivity", "com.sec.android.app.camera.Camera");//specify startup flags appium sever to execute adb shell commandsvar serveroptions = new OptionCollector();var relaxedSecurityOption = new KeyValuePair<string, string>("--relaxed-security", "");serveroptions.AddArguments(relaxedSecurityOption);var _appiumLocalService = new AppiumServiceBuilder().UsingAnyFreePort().WithArguments(serveroptions).Build();//start the service_appiumLocalService.Start();var driver = new AndroidDriver<AndroidElement>(_appiumLocalService, capabilities);return driver;}
6.2.1 完善TestMethod
[ClassCleanup]//clear up local service
static public void CleanUp()
{_appiumLocalService?.Dispose();_appiumLocalService = null;
}[TestMethod]
public void TestListInstallPackage()
{AndroidDriver<AndroidElement> driver = StartApp();//use shell command to list all installed package on the devicestring script = "mobile:shell";var arguments = new Dictionary<string, string>{{"command","pm list package" },{ "----show-versioncode",""},};var list = driver.ExecuteScript(script, arguments);Assert.IsNotNull(list);Console.Write(list);
}
然后点击运行测试,就可以看到手机相机启动了。
Appium - 入门指南(偏重于实际操作)相关推荐
- JavaCV入门指南:调用opencv原生API和JavaCV是如何封装了opencv的图像处理操作?
JavaCV入门指南系列: JavaCV入门指南:序章(看完本章后,不想看原理的小伙伴可直接跳转到<快速上手篇>) JavaCV入门指南:调用FFmpeg原生API和JavaCV是如何封装 ...
- javaCV入门指南:调用FFmpeg原生API和JavaCV是如何封装了FFmpeg的音视频操作?
JavaCV入门指南系列: JavaCV入门指南:序章(看完本章后,不想看原理的小伙伴可直接跳转到<快速上手篇>) JavaCV入门指南:调用FFmpeg原生API和JavaCV是如何封装 ...
- 计算机网络新手实操,嗜血印新手操作入门指南 游戏基础玩法攻略介绍
<嗜血印>是一款国产的武侠动作类游戏,游戏已经在STEAM上正式上市,售价39元,喜欢该类型的玩家可以考虑进行购买,下面就为大家带来嗜血印新手操作入门指南. 上手指南: 按键操作: 前/后 ...
- TensorFlow2 入门指南 | 06 TensorFLow2 高阶操作汇总
前言: 本专栏在保证内容完整性的基础上,力求简洁,旨在让初学者能够更快地.高效地入门TensorFlow2 深度学习框架.如果觉得本专栏对您有帮助的话,可以给一个小小的三连,各位的支持将是我创作的最大 ...
- 初级软件测试工程师零基础入门指南
初级软件测试工程师零基础入门指南 唐井军 编著 2012年10月 1.基本概念 1.1软件 软件就是可以在计算机上运行的计算机程序,如操作系统Windows.办公软件Office.聊天QQ.手机游戏等 ...
- [CTF] CTF入门指南
CTF入门指南 何为CTF ? CTF(Capture The Flag)夺旗比赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式.CTF起源于1996年DEFCON全球黑客大会 ...
- 入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集-深度学习问题
入门指南目录页 -PaddlePaddle 飞桨 入门指南 FAQ合集 GT_Zhang关注 0.1012019.08.01 18:43:34字数 1,874阅读 795 Hi,欢迎各位来自Paddl ...
- mongoDB 入门指南、示例
http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html mongoDB 入门指南.示例 上一篇:简单介绍mongoDB 一.准备工作 ...
- 数据分析从头学_数据新闻学入门指南:让我们从头开始构建故事
数据分析从头学 by Mina Demian 由Mina Demian 数据新闻学入门指南:让我们从头开始构建故事 (A Beginner's Guide to Data Journalism: Le ...
最新文章
- Xshell使用xftp传输文件,使用pure-ftpd搭建ftp服务
- 宝付分析程序员怎么提升自己
- QT中封装的IP地址的widget
- 保定linux第一版PPT-SVN for Linux
- python-数据结构-栈
- 通过例子学Solidity[注释翻译]
- 微信小程序点击文字变色
- 苹果开售官翻iPhone XR机型 最多比新机便宜120美元
- PS 如何制作 圆角矩形 图片
- 谈一谈|Word文档图片的提取
- python控制台打印文字logo
- vertical-align属性的简单理解
- [pwn][堆利用]house of spirit[例题:lctf2016_pwn200]
- HTML静态网页作业——关于我的家乡介绍安庆景点
- Windows 7安装软件时无法将注册值写入注册表的处理方法
- 环境实时监控系统的设计(以物联网仓储为例)
- VMWare使用鼠标额外的功能键
- Java手写单例模式
- 基于业务分离的Android开发框架MVB,MVC思想的android实现
- 微商洗脑广告文案 微商顶级文案大全 整合下载