简体   繁体   English

watir 浏览器对象可以在以后的 Ruby 进程中重用吗?

[英]Can a watir browser object be re-used in a later Ruby process?

So let's say pretty often a script runs that opens a browser and does web things:因此,假设经常运行一个脚本来打开浏览器并执行 Web 操作:

require 'watir-webdriver'

$browser = Watir::Browser.new(:firefox, :profile => "botmode")
 => #<Watir::Browser:0x7fc97b06f558 url="about:blank" title="about:blank"> 

It could end gracefully with a browser.close, or it could crash sooner and leave behind the memory-hungry Firefox process, unnoticed until they accumulate and slow the server to a crawl.它可以优雅地以 browser.close 结束,或者它可以更快地崩溃并留下内存饥渴的 Firefox 进程,直到它们累积并使服务器缓慢爬行时才会被注意到。

My question is twofold:我的问题是双重的:

  • What is a good practice to ensure that even in case of script failure anywhere leading to immediate error exit, the subprocess will always get cleaned up (I already have lots of short begin-rescue-end blocks peppered for other unrelated small tests)有什么好的做法可以确保即使在任何脚本失败导致立即错误退出的情况下,子进程也将始终得到清理(我已经有很多短的开始-救援-结束块用于其他不相关的小测试)
  • More importantly, can I simply remember this Watir::Browser:0x7fc97b06f558 object address or PID somehow and re-assign it to another $browser variable in a whole new Ruby process, for example irb?更重要的是,我能不能简单地记住这个 Watir::Browser:0x7fc97b06f558 对象地址或 PID 并以某种方式将它重新分配给一个全新的 Ruby 进程中的另一个 $browser 变量,例如 irb? Ie can an orphaned browser on webdriver be re-attached later in another program using watir-webdriver on the same machine?即可以稍后在同一台机器上使用 watir-webdriver 将 webdriver 上的孤立浏览器重新附加到另一个程序中吗? From irb I could then get in and re-attach to the browser left behind by the crashed Ruby script, to examine the website it was on, check what went wrong, what elements are different than expected, etc.然后我可以从 irb 进入并重新连接到崩溃的 Ruby 脚本留下的浏览器,检查它所在的网站,检查出了什么问题,哪些元素与预期不同,等等。

Another hugely advantageous use of the latter would be to avoid the overhead of potentially hundreds of browser startups and shutdowns per day...best to keep one alive as sort of a daemon.后者的另一个非常有利的用途是避免每天可能有数百次浏览器启动和关闭的开销......最好让一个像守护进程一样活着。 The first run would attempt to reuse a previous browser object using my specially prepared botmode profile, otherwise create one.第一次运行将尝试使用我特别准备的botmode配置文件重用以前的浏览器对象,否则创建一个。 Then I would deliberately not call $browser.close at the end of my script.然后我会故意不在我的脚本末尾调用 $browser.close 。 If nothing else I run an at job to kill the Xvfb :99 display FF runs inside of at the end of the day anyway (giving FF no choice but to die with it, if still running).如果没有别的,我运行一个 at 作业来杀死 Xvfb :99 显示 FF 无论如何都会在一天结束时在里面运行(让 FF 别无选择,只能随它而死,如果仍在运行)。 Yes I am aware of Selenium standalone jar, but trying to avoid that java service footprint too.是的,我知道 Selenium 独立 jar,但也试图避免 Java 服务的足迹。

Apologies if this is more a basic Ruby question.抱歉,如果这更像是一个基本的 Ruby 问题。 I just wasn't sure how to phrase it and keep getting irrelevant search results.我只是不确定如何措辞并不断获得不相关的搜索结果。

I guess, U cant just remember the variable from another process.我想,你不能只记住另一个进程的变量。 But the solution might be creating a master process and process your script in loop in thread, periodically checking the browser running state.但解决方案可能是创建一个主进程并在线程中循环处理您的脚本,定期检查浏览器运行状态。 I'm using some thing similar in my acceptance tests on Cucumber + watir.我在 Cucumber + watir 的验收测试中使用了一些类似的东西。 So it will be some thing like that:所以它会是这样的:

require 'rubygems'
require 'firewatir' # or watir
@browser = FireWatir::Firefox.new

t = Thread.new do
    @browser.goto "http://google.com"
    #call more browser actions here
end
while not_exit?
  if t.stop?
      # error occurred in thread, restart or exit
  end
  if browser_live?
      # browser was killed for a some reason
      # restart or exit
  end
end
@browser.close

not_exit?不_退出? - can be over TRAP for the ctrl+C - 可以通过 ctrl+C 的 TRAP

browser_live?浏览器直播? - you can check if firefox browser running with processes listings - 您可以检查 firefox 浏览器是否与进程列表一起运行

It is quite tricky but might work for you这很棘手,但可能对你有用

You can use DRb like this:您可以像这样使用DRb
browsers pool:浏览器池:

require 'drb'
require 'watir'

browser = Watir::Browser.new :chrome
DRb.start_service 'druby://127.0.0.1:9395', browser

gets

and then from test script use this browser:然后从测试脚本使用这个浏览器:

require 'drb'
browser = DRbObject.new_with_uri 'druby://127.0.0.1:9395'
browser.goto 'stackoverflow.com'

I'm pretty sure that at the point ruby exits, any handles or pointers to something like a browser object would become invalid.我很确定在 ruby​​ 退出时,任何指向浏览器对象之类的句柄或指针都将无效。 So re-using something in a later ruby process is likely not a good approach.因此,在以后的 ruby​​ 过程中重用某些东西可能不是一个好方法。 In addition I might be wrong on this, but it does seem that webdriver is not very good at connecting to a running browser process.此外,我在这方面可能是错的,但 webdriver 似乎不太擅长连接到正在运行的浏览器进程。 So for your approach to work it would really all need to be wrapped by some master process that was calling all the tests etc.. and hey wait a sec, that's starting to sound like a framework, which you might already (or perhaps should be) using in the first place.因此,对于您的工作方法,它确实需要由一些调用所有测试等的主进程包装。 ) 首先使用。

So a better solution is probably to look at whatever framework you are using to run your tests and investigate any capability for 'setup/teardown' actions (which can go by different names) which are run before and after either each test, groups of tests, or all tests.因此,更好的解决方案可能是查看您用于运行测试的任何框架,并调查在每个测试、测试组之前和之后运行的“设置/拆卸”操作(可以使用不同名称)的任何功能,或所有测试。 Going this way is good since most frameworks are designed to allow you to run any single test, or set of tests that you want to.这样做很好,因为大多数框架旨在允许您运行任何单个测试或您想要的一组测试。 And if your tests are well designed they can be run singly without having to expect the system was left in some perfect state by a prior test.如果您的测试设计良好,则它们可以单独运行,而不必期望系统在先前的测试中处于某种完美状态。 Thus these sorts of setup/teardown actions are designed to work that way as well.因此,这些类型的设置/拆卸操作也旨在以这种方式工作。

As an example Cucumber has this at the feature level, with the idea of a 'background' which is basically intended as a way to dry out scenarios by defining common steps to run before each scenario in a feature file.例如,Cucumber 在功能级别具有此功能,其“背景”概念基本上旨在通过定义在功能文件中的每个场景之前运行的通用步骤来使场景变干。 (such as navigating to and logging into your site) This could include a call to a series of steps that would look to see if a browser object existed, and if not create one. (例如导航并登录到您的站点)这可能包括调用一系列步骤,以查看浏览器对象是否存在,如果不存在则创建一个。 However you'd need to put that in every feature file which starts to become rather non dry.但是,您需要将其放入每个开始变得相当不干燥的功能文件中。

Fortunately cucumber also allows a way to do this in one place via the use of Hooks .幸运的是,cucumber 还允许通过使用Hooks在一个地方做到这一点。 You can define hooks to run before steps, in the event of specific conditions, 'before' and 'after' each scenario, as well as code that runs once before any scenarios, and code defined to run 'at_exit' where you could close the browser after all scenarios have run.您可以定义在步骤之前运行的钩子,在特定条件的情况下,每个场景的“之前”和“之后”,以及在任何场景之前运行一次的代码,以及定义为运行“at_exit”的代码,您可以在其中关闭在所有场景运行后浏览器。

If I was using cucumber I'd look at the idea of a some code in env.rb that would run at the start to create a browser, complemented by at_exit code to close the browser.如果我使用的是黄瓜,我会看看env.rb中的一些代码的想法,该代码将在开始时运行以创建浏览器,并辅以 at_exit 代码以关闭浏览器。 Then perhaps also code in a before hook which could check to see that the browser is still there and re-create it if needed, and maybe logout actions in a after hook.然后也许还可以在before钩子中编码,它可以检查浏览器是否仍然存在并在需要时重新创建它,并且可能在after钩子中注销操作。 Leave stuff like logging in for the individual scenarios, or a background block if all scenarios in a feature login with the same sort of user.如果功能中的所有场景都使用相同类型的用户登录,请留下诸如登录单个场景之类的东西,或者一个background块。

Not so much a solution but a workaround for part 1 of my question, using pkill.与其说是解决方案,不如说是我问题的第 1 部分的解决方法,使用 pkill。 Posting here since it turned out to be a lot less trivial than I had hoped.在这里发帖,因为结果证明它比我希望的要简单得多。

After the ruby script exits, its spawned processes (which may not at all belong in the same PID tree anymore, like firefox-bin) have a predictable "session leader" which turned out to be the parent of the bash shell calling rubyprogram.rb in my case. ruby 脚本退出后,它产生的进程(可能根本不再属于同一个 PID 树,如 firefox-bin)有一个可预测的“会话领导者”,结果证明它是调用 ruby​​program.rb 的 bash shell 的进程就我而言。 Available as $PPID in Bash, for when you have to go higher than $$ .在 Bash 中作为$PPID可用,当你必须高于$$ 时

Thus to really clean up unwanted heavyweight processes eg.因此,真正清理不需要的重量级进程,例如。 after a ruby crash:红宝石崩溃后:

#!/bin/bash
# This is the script that wraps on top of Ruby scripts

./ruby_program_using_watirwebdriver_browser.rb  myparams &  # spawn ruby in background but keep going below:

sleep 11 # give Ruby a chance to launch its web browser

pstree -panu $$  # prints out a process tree starting under Bash, the parent of Ruby. Firefox may not show!

wait  # now wait for Ruby to exit or crash

pkill -s $PPID firefox-bin  # should only kill firefox-bin's caused above, not elsewhere on the system

# Another way without pkill, will also print out what's getting killed if anything:
awk '$7=="firefox-bin" && $3=="'$PPID'" {print $1}' <(ps x -o pid,pgid,sess,ppid,tty,time,comm) | xargs -rt kill

OPTIONAL And since I use a dedicated Xvfb Xwindows server just for webdriving on DISPLAY :99, I can also count on xkill:可选而且由于我使用专用的 Xvfb Xwindows 服务器仅用于 DISPLAY :99 上的网络驱动,因此我还可以依靠 xkill:

timeout 1s  xwininfo -display :99 -root -all |awk '/("Navigator" "Firefox")/ {print $1}' |xargs -rt  xkill -display :99 -id  
# the timeout is in case xkill decides to wait for user action, when window id was missing

Just an update on part 2 of my question.只是我问题的第 2 部分的更新。

It seems one CAN serialize a Watir:Browser object with YAML, and because it's text-based the contents were quite interesting to me (eg some things I've only dreamed of tweaking hidden inside private elements of private classes...but that's a separate topic)似乎可以使用 YAML 序列化 Watir:Browser 对象,并且因为它是基于文本的,所以内容对我来说非常有趣(例如,我一直梦想着调整隐藏在私有类的私有元素中的某些东西......但这是一个单独的主题)

Deserializing from YAML is still trouble.从 YAML 反序列化仍然很麻烦。 While I haven't tested beyond the first try it gives me some kind of reg exp parse error...not sure what that's about.虽然我没有在第一次尝试之后进行测试,但它给了我某种 reg exp 解析错误......不确定那是什么。

(more on that at at how to serialize an object using TCPServer inside? ) (更多关于如何在内部使用 TCPServer 序列化对象?

Meanwhile, even attempting to serialize with Marshal, which is also built-in to Ruby but stores in binary format, results in a very reasonable-sounding error about not being able to dump a TCPServer object (apparently contained within my Watir:Browser pointed to by $browser)同时,即使尝试使用 Marshal 进行序列化,Marshal 也是 Ruby 内置的,但以二进制格式存储,也会导致无法转储 TCPServer 对象(显然包含在我的 Watir:Browser 中指向的一个非常合理的错误)由$浏览器)

All in all I'm not surprised at these results, but still pretty confident there is a way, until Watir arrives at something more native (like PersistentWebdriver or how it used to be in the days of jssh when you could simply attach to an already running browser with the right extension)总而言之,我对这些结果并不感到惊讶,但仍然非常有信心有一种方法,直到 Watir 到达更原生的东西(比如PersistentWebdriver或者它曾经是如何在 jssh 时代,当你可以简单地附加到一个已经使用正确的扩展程序运行浏览器)

Until then, if serialization + deserialization to a working object gets too thorny I'll resort to daemonizing a portion of my Ruby to keep objects persistent and spare the frequent and costly setup/teardowns.在那之前,如果对工作对象的序列化 + 反序列化变得过于棘手,我将诉诸于守护我的 Ruby 的一部分以保持对象的持久性并避免频繁且昂贵的设置/拆卸。 And I did take a gander at some established (unit testing) frameworks but none seem to fit well yet within my overall software structure--I'm not web testing after all.我确实对一些已建立的(单元测试)框架进行了观察,但似乎没有一个框架适合我的整体软件结构——毕竟我不是网络测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM