minkphp / mink Goto Github PK
View Code? Open in Web Editor NEWPHP web browser emulator abstraction
Home Page: https://mink.behat.org/
License: MIT License
PHP web browser emulator abstraction
Home Page: https://mink.behat.org/
License: MIT License
When you need to validate the existence of a string which has double quotes you'll run into problems. Here's an example:
And I should see "The username "Mathew" has already been used."
I have two solutions for solving this, I much prefer the 1st as this allows to make sure any output escaping you're doing is working.
This would allow you to write the above rule as:
And I should see "The username "Mathew" has already been used."
This is more verbose, but it's what the browser has returned. I don't believe any transformations should be done as it can screw up validating data has been properly escaped by your website.
All we need to do here is make the regular expression greedy. This can be solved using possessive quantifiers, so using:
"(?P<text>[^"]*+)"
should get you the correct result
Thanks.
hi there!
another issue, please don't kill me if it's a dumb failure. but it took me almost 2.5 hours today to pin down the line of code which made my authentication (form and facebook) completly disfunctional - didn't think that it could have something to do with the mink installation.
in config_dev.yaml 2 lines:
framework: test: ~
can you tell me what they do?
Hi,
I'm having trouble with Mink and GoutteDriver in this special case. I have a form with one action and several buttons. Every button has its name and based on the submitted data array I find out if user pressed button A, or button B, C....
However Goutte always submits form referencing to first submit button in DOM tree. If there is none (only ordinary type="button"), no matter what id|title|name|value of button I pass it always just submitts the form without any of the form button data. If there are many submit buttons, no matter what id|title|name|value of button I pass it always just submitts the form with the first button data.
So I tried to fake it with hidden field, but there is no way to change the value for hidden fields based on the pressed button in my own steps or in FeatureContext. Or am I wrong?
My other shot was to "clone" click() method from GoutteDriver and add the name of clicked button into parameters array on the fly. But there is the catch that $forms array storing already filled forms with data is private, so there is no way to update the form values before the actual request. Either I loose what was checked/selected/filled in, or I don't know what action shall I execute.
Is there any way around this? Or is it something that I can call issue?
Thanks, luku
If for example you assert that a page contains a text and the page is empty, you get the following message:
Then I should see "LOREM" # Behat\Mink\Behat\Context\PageContext::assertPageContainsText()
Argument #2 of PHPUnit_Framework_Assert::assertRegExp() must be a string
which is misleading.
We're experiencing problems running the ZombieDriver with zombie.js. It hangs Behat when it attempts to start the node.js server.
The "stable" version of node.js that we're using is 0.6.0 from http://nodejs.org/#download
The problem seems to be caused by the fact that some part of Zombie.js is using the 'sys' module in Node, which is deprecated - as far as I can tell from http://sixarm.com/about/node_js_socket_io_fix_for_sys_and_util.html
Because of the deprecation, starting with version 0.6.0 node.js will print the following error on stderr upon running Mink's script: "The "sys" module is now called "util". It should have a similar interface"
Mink doesn't expect this appearing on stderr and thinks that zombie.js cannot be started (despite it actually starting regardless). If it sees any output from node.js on stderr at all, it throws an exception.
However, it doesn't run the pipe cleanup code and proc_close before throwing the exception in spawnZombieProcess() - this seems to cause a non-deterministic deadlock in another part of the code when the exception causes an early exit from the spawnZombieProcess() function.
I've just upgraded to 1.2 and it appears that when using the goutte driver each request is creating a new session
If I revert to 1.1.1 everything works fine
I'm running on php 5.3.8, osx 10.7.2, behat 2.1.3
Some more steps are needed for things like status code and response headers.
Great work on mink btw, it's really making my life testing my application far easier.
It's possible to send a username and password in the URL, as described in RFC 1738.
Yet it works with Sahi.
Form with input type="file" regular form submission without javascript.
right now it seems to require bbd80ebd50510217597d4d9a75b57564cb8a7b32
using current master leads to "Notice: Undefined property: Buzz\Message\Request::$getContent"
see also http://screencast.com/t/QG6Ejf3N
Looks like it is a problem with Goutte as I can use Sahi to do the test and it'll work fine.
Scenario: Empty form submission
Given I am logged in as "[email protected]"
And I am on "/upload"
When I press "Upload"
Then show last response
Unable to read file '' for upload
I guess you are implementing this already but just trying to fire up a discussion too :)
I saw the commit with arrays hmmm nice!
can we change this line to allow for an option for symfony driver to output on specific directory?
https://github.com/Behat/Mink/blob/master/src/Behat/Mink/Behat/Context/MinkContext.php#L613
I have noticed that in Behat\Mink\Driver\SeleniumDriver waitForPageToLoad is not executed because $readyState is "complete"
while it should be "loading" or "interactive"
In result when i do visit($url) after click() on a (for example) login form i go to $url before I am logged in.
public function click($xpath) {
$this->browser->click(SeleniumLocator::xpath($xpath));
$readyState = $this->browser->getEval('window.document.readyState');
if ($readyState == 'loading' || $readyState == 'interactive') {
$this->browser->waitForPageToLoad($this->timeout);
}
}
It looks like the problem is with what window.document.readyState returns, but maybe $readyState verification is not in the right place ?
After commenting out that if everything works fine for me.
@everzet have you tested sahi well? I think the tests will depend too much on timing issues, speed of response etc, it is a real browser, anything can happen so I don't think it is deterministic, one day the test fails the other not, that is how it is going for me
Nothing big, just a minor thing, but I stumbled over it today; so I thought I would share this.
ActionableElement::clickButton()
MinkContext::pressButton()
In the feature files, it is also press "xyz"
.
Maybe rename everything to "pressButton", or is there a semantical difference I'm missing?
Is it possible to set a proxy when using Goutte with mink?
I am issuing almost consecutive calls of "I am on"/"I go to" and get this error while using zombie, the problem does not occur on sahi or other:
@mink:zombie
Given I am on "/login" # Bundle\Features\Context\MyMinkContext::visit()
And I login # Bundle\Features\Context\FeatureContext::iLogin()
And I go to "/page" # Bundle\Features\Context\MyMinkContext::visit()
Could not establish connection: Connection refused (111)
It seems it gets confused and cannot find the step definition. Is this a bug?
Hello!
I propose to change the format of output for tests that use tables to linear: that is, for each line in a table, a full test trace is printed.
Rationale:
When I run a test with tables, it outputs the folowing:
Структура сценария: 1. Администратор может редактировать внешнего пользователя. # test/features/apps/frontend/user_edit.feature:5
Когда я захожу в систему как "[email protected]" # test/features/steps/mask_steps.php:8
И открываю страницу профиля пользователя "Администратор" # test/features/steps/mask_steps.php:21
То я должен видеть "Изменить пользователя" # lib/mink/src/Behat/Mink/Integration/steps/mink_steps.php:62
И кликаю по ссылке "Изменить пользователя" # lib/mink/src/Behat/Mink/Integration/steps/mink_steps.php:27
И заполняю поле "<Поле>" значением "<Значение>" # lib/mink/src/Behat/Mink/Integration/steps/mink_steps.php:31
И нажимаю "Сохранить" # lib/mink/src/Behat/Mink/Integration/steps/mink_steps.php:23
И кликаю по ссылке "Изменить пользователя" # lib/mink/src/Behat/Mink/Integration/steps/mink_steps.php:27
То поле "<Поле>" должно содержать "<Значение>" # lib/mink/src/Behat/Mink/Integration/steps/mink_steps.php:76
Значения:
| Поле | Значение |
| E-mail | new_admin@mattino.ru |
link with locator: "Изменить пользователя" not found
| Отображаемое имя | Новое имя администратора |
link with locator: "Изменить пользователя" not found
2 scenarios (2 failed)
16 steps (12 passed, 2 skipped, 2 failed)
0m31.526s
However, recently, I had a hard time debugging my app, because the test output didn't give me a clue as to what step was actually failing. The first «И кликаю по ссылке "Изменить пользователя"» run normally. Only by noticing a strange count of passed/skipped/failed steps did I recognize that the second «И кликаю по ссылке "Изменить пользователя"» made the test fail.
If the format was linear, I would see the green/red lines as with usual tests, and that would make debugging easier.
Have got some error with deep traversing some time ago. Need to check.
It seems i am getting an error like this when trying MinkContext support for subcontexts
[ErrorException]
Catchable Fatal Error: Argument 1 passed to Bundle\Features\Context\ClickingContext::__construct() must implement interface Symfony\Component\HttpKernel\HttpKernelInterface, none given, called in /home/cordoval/sites-2/Bundle/Features/Context/FeatureContext.php on line 28 and defined in /home/cordoval/sites-2/Bundle/Features/Context/ClickingContext.php line 16
one file
class FeatureContext extends MinkContext //BehatContext if you want to test web
{
public function __construct(HttpKernelInterface $kernel) {
$this->useContext(new ClickingContext());
parent::__construct($kernel);
}
//
second file
class ClickingContext extends MinkContext {
public function __construct(HttpKernelInterface $kernel) {
parent::__construct($kernel);
}
can anyone help how it is done the right way? or is this a bug?
If it is not the right way could you please tell me how to do it right?
Thanks!
Can we support boolean selector here
https://github.com/Behat/Mink/blob/master/src/Behat/Mink/Element/TraversableElement.php#L263
for the case
<div class="field">
<label>Waiver</label>
<div id="waiver">
<input type="radio" id="waiver_1" value="1">
<label for="waiver_1"> Yes </label>
<input type="radio" id="waiver_0" value="0">
<label for="waiver_0"> No </label></div>
</div>
so we can do
Then I select "yes" from "Waiver" ?
Thanks
checkField does not work with symfony driver
it does work with sahi driver though
other methods work well but this one
I just wanted to point out, that you tagged the last version with 3.0.1 and because the previous was 0.3.0 I think this was a mistake and has to be 0.3.1 instead.
PhantomJS is a headless WebKit with JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.
https://github.com/ariya/phantomjs
Sounds like a candidate for a Mink Driver.
When I use sahi and goutte with this I should see step it works
when I set driver to symfony it fails ... perhaps it is too much of a check? please let me know how to approach it and if I can work on a PR
perhaps flash messages is meant not to work on symfony driver...
Thanks
I'm getting a blank response when trying to query/visit "windows-1255" websites, for example:
$url = "http://www.yad2.co.il/";
$session->visit($url);
// returns blank
echo $session->getPage()->getContent();
MinkContext#assertFieldContains
, MinkContext#assertFieldNotContains
and some other assertion methods use variable $element
but this variable isn't defined anywhere in the method.
public function assertFieldContains($field, $value)
{
// .. some code
try {
assertEquals($value, $field->getValue());
} catch (\PHPUnit_Framework_ExpectationFailedException $e) {
// Look at the line below
$message = sprintf('Form field with id|name|label|value "%s" has "%s" value, but should have "%s"', $element, $field->getValue(), $value);
throw new ExpectationException($message, $this->getSession(), $e);
}
}
This leads to errors when running behat:
And the "birthday" field should contain "1991-10-03" # FeatureContext::assertFieldContains()
Notice: Undefined variable: element
when it should have outputted something like:
And the "birthday" field should contain "1991-10-03" # FeatureContext::assertFieldContains()
And the "birthday" field should contain "1991-10-03" # FeatureContext::assertFieldContains()
Form field with id|name|label|value 'birthday" has "1991-10-03" value, but should have "1990-10-03"
Some unit tests for the ZombieDriver
are failing after an update of zombie.js from 0.9.x to 0.10.x
I'm currently looking into it.
Please refrain from updating to version 0.10.x until this is fixed.
It would be great if Mink could support choosing multiple options from a multiple select field.
Given the following:
<select id="cars" multiple="multiple">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</select>
Something like this
$this->getSession()->getPage()->selectFieldOption('cars', array('mercedes','audi'));
Should result in both Audi and Mercedes being selected.
When a test using sahi for browser emulation fails the xml output (specifically at the <failure>
tag) is not valid. Looks like the embedded CDATA is causing issues...
When running a test with a simple feature/context and with Behat 2.0.5/Mink 1.1.1/PHP 5.3.5
I get this error:
$ behat
Feature: zombtest
...usual stuff ....
[RuntimeException]
Can not instantiate server (node /tmp/mink_zombie_servereH68Kq):
node.js:134
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: EADDRINUSE, Address already in use
at Server._doListen (net.js:1106:5)
at net.js:1077:14
at Object.lookup (dns.js:159:5)
at Server.listen (net.js:1071:20)
at Object.<anonymous> (/tmp/mink_zombie_servereH68Kq:27:4)
at Module._compile (module.js:402:26)
at Object..js (module.js:408:10)
at Module.load (module.js:334:31)
at Function._load (module.js:293:12)
at Array.<anonymous> (module.js:421:10)
I did some digging and it seems that after successfully creating a zombie server (which I can see it's file in my /tmp directory and can see its node process spawned in my OS (ubuntu) ), the Server object tries to create ANOTHER process despite one already existing. This seems because $this->process is null every time the isRunning() method checks. the start() method is called more than once which I guess triggers that error.
My test feature file:
Feature: zombtest
In order to browse my account
As a Promoter
I need to log in
Scenario:
Given I have browsed to "/account"
My Context file:
<?php
use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\TranslatedContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
require_once 'mink/autoload.php'; //we will be acceptance testiing
class FeatureContext extends BehatContext
{
public function __construct(array $parameters)
{
$this->mydriver = new \Behat\Mink\Driver\ZombieDriver();
$this->mysession = new \Behat\Mink\Session($this->mydriver);
$this->mysession->start();
}
/**
* @Given /^I have browsed to "([^"]*)"$/
*/
public function iHaveBrowsedTo($argument)
{ //never makes it here
$this->mysession->visit("http://dev.local/" . $argument);
echo "current url " . $this->mysession->getCurrentUrl() ;
}
}
It would be nice to have a step that counts the number of elements on a page. Pull request incoming.
I think it would make sense to add support for some aria roles like "button", and "link" for example. See also
http://www.w3.org/TR/wai-aria
If I have mark-up such as the following:
<div role="button">Some Button Name</div>
and a scenario such as:
Scenario: ABC
Given I am on "/abc/"
And I press "Some Button Name"
then this fails atm because it tells me no button is found.
Is it possible to select the option by id|value|text etc. rather than just specify the value. As far as I can see when using the step relating to select elements you can specify the select element by id|name|label|value but the value given in the step is just set to that select element. I was expecting to be able to specify the option by id or text rather than having to know the actual value.
For example:
When I select "Test Option" from "testSelect"
I get the following error:
Input "testSelect" cannot take "Test Option" as a value (possible values: testValue).
It does not work specifying the id of the option element either.
For example : given client A do an action, and client B do an action then client A do another action
When using Behat + Mink + zombie.js
if you define a parameter 'base_url' in a config.yml file like this
default:
context:
parameters:
base_url: http://my_url/
When running your test with something like "behat -c config/config.yml", this parameter is correctly sent to MinkContext instance, but not used. So zombie.js tries to reach localhost instead of configured url.
hi there!
mink is awesome - thank you!
unfortunatelly i get an error after running the tests a second time:
"PHP Fatal error: Class 'Behat\SahiClient\Expection\ConnectionException' not found in /Library/WebServer/Documents/eventiply/vendor/Behat/SahiClient/src/Behat/SahiClient/Client.php on line 64"
When i restart the sahi service, and restart the test it works very well again- but just once :/
BR Stephan
Hello,
the function fillField(); dont works on html field like:
It's a bug or it's a feature ?
Thank you.
$ behat --version
Behat version 1.1.9
Mink version: 0.3.2
Trying to use a css id selector to fill a field:
Given I fill in "#s" with "San Francisco, CA"
Behat fails to parse the file:
And I fill in "
And I fill in "
[....]
You can implement step definitions for undefined steps with these snippets:
$steps->And('/^I fill in "$/', function($world) {
throw new \Behat\Behat\Exception\Pending();
});
It seems to be related to the parser where characters after the hash # are stripped of.
Mink has a set of built in steps which might need overloading in certain cases. If we try to overload those steps we either get an error about the step being defined elsewhere or an ambiguous error.
A CFS (Cascading Filesystem) would work well here, application level steps would get precedence over system steps (Mink).
There seems to be a bug in the Sahi Driver when you want to fill out form fields.
My scenario looks like this:
@database @userFixture @javascript
Scenario: Access Profile Page For the First Time
Given I am logged in as normal user
And I am on "/profile/"
Then I should see a list of all current posts
If I run it without @javascript, then everything works fine. When running with @javascript, Sahi doesn't seem to fill in the form fields, but simply submits the login form without any values. I'm using Sahi 3.5, and latest Behat/Mink version.
Behat version 1.1.4
I believe it's to do with this:
enctype="multipart/form-data"
Works in the browsers I've tested, but Mink does not send this attribute along with the request and causes my code to fail ($_FILES no longer exists). I confirmed the bug by removing that attribute and getting the same error in the browser.
Undefined index: coaster
$coaster_ext = $_POST['coaster_ext'] = pathinfo($_FILES['coaster']['name'], PATHINFO_EXTENSION);
Scenario: Empty form submission
Given I am logged in with "test@localhost"
And I am on /upload
When I press "Upload"
Then I should see "A name is required."
<form enctype="multipart/form-data" action="" method="POST">
{{&csrf_token}}
{{#form}}
<fieldset>
<legend>1. Coaster Details</legend>
<p id="name">
<label>Name <span class="required">*</span></label>
<input type="text" name="name" value="{{name}}">
</p>
<p id="description">
<label>Description (<a href="http://daringfireball.net/projects/markdown/basics" rel="no-follow">Formatting Basics</a>)<span class="required">*</span></label>
<textarea value="{{description}}" name="description"></textarea>
</p>
</fieldset>
<fieldset>
<legend>2. Files</legend>
<p id="coaster">
<label>Coaster File: <strong>nltrack</strong> or <strong>nlpack</strong> & no larger than 35MB<span class="required">*</span></label>
<input size="50" type="file" name="coaster">
</p>
<p id="screenshot">
<label>Screenshot: <strong>jpg</strong>, <strong>jpeg</strong> or a <strong>png</strong> & no larger than 1MB<span class="required">*</span></label>
<input size="50" type="file" name="screenshot">
</p>
<p id="submit">
<label id="guidelines"><input type="checkbox" name="guidelines" {{guidelines}}> I've read the guidelines and understand them.</label>
<button>Upload</button>
</p>
</fieldset>
</form>
{{/form}}
When running zombie, Mink does not close the process which causes this error:
[mathew@thepixeldeveloper (develop) public]$ behat behat/features/login.feature
F-.............................
(::) failed steps (::)
01. Can not instantiate server (node /tmp/mink_zombie_serverhVo6t2):
node.js:134
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: EADDRINUSE, Address already in use
at Server._doListen (net.js:1100:5)
at net.js:1071:14
at Object.lookup (dns.js:159:5)
at Server.listen (net.js:1065:20)
at Object.<anonymous> (/tmp/mink_zombie_serverhVo6t2:27:4)
at Module._compile (module.js:402:26)
at Object..js (module.js:408:10)
at Module.load (module.js:334:31)
at Function._load (module.js:293:12)
at Array.<anonymous> (module.js:421:10)
In step `Given I am on "/login"'. # FeatureContext::visit()
From scenario `Title'. # behat/features/login.feature:4
9 scenarios (8 passed, 1 failed)
31 steps (29 passed, 1 skipped, 1 failed)
0m2.957s
How zombie is being used:
@mink:zombie
Scenario: Title
Given I am on "/login"
Then I should see "Login - Website" in the "title" element
Additional
nodejs --version
v0.4.10
not sure how to find out the zombiejs version.
I am having an issue with Mink+Zombie. It all started with me not being able to visit a page. I tried a million times with a dozen sites, and always got:
PHP Fatal error: Uncaught exception 'Behat\Mink\Exception\DriverException' with message 'Could not load resource for URL 'http://www.grahamc.com'' in /Users/travisblack/Sites/nationalfield/lib/vendor/Mink/src/Behat/Mink/Driver/ZombieDriver.php:170
I removed the !empty check from the Zombie driver, and tried, then readded the check, and to my surprise, I got different results, but I think it is a result of caching. Now, I am able to visit a page, and get a response, but there seems to be no content in the pages, ie:
<?php
require_once __DIR__ . '/../lib/vendor/Mink/autoload.php';
$driver = new \Behat\Mink\Driver\ZombieDriver();
// init session:
$session = new \Behat\Mink\Session($driver);
// start session:
$session->start();
$session->visit('http://testing.nf.localhost/frontend_dev.php');
// get the current page URL:
echo $session->getCurrentUrl();
// get the response status code:
echo $session->getStatusCode();
// get page content:
echo $session->getPage()->getContent();
returns http://testing.nf.localhost/frontend_dev.php200
but doesn't return any content from the page.
When testing in an actual scenario, I tried:
@mink:zombie
Feature: app
In order to use NationalField
As a user with a browser
I need to be able to access the NationalField site
Scenario: Access the login page
Given I am on "/"
When I reload the page
Then I should see "Welcome back"
But it returns:
Feature: app
In order to use NationalField
As a user with a browser
I need to be able to access the NationalField site
@mink:zombie
Scenario: Access the login page # features/app.feature:7
Given I am on "/" # FeatureContext::visit()
When I reload the page # FeatureContext::reload()
Then I should see "Welcome back" # FeatureContext::assertPageContainsText()
Argument #2 of PHPUnit_Framework_Assert::assertContains() must be a array, iterator or string
Again, hinting that the page is empty.
Hi,
Even that a new rules appears :
When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/ - Selects additional option in select field with specified id|name|label|value
.
When I write
`When I select "INST_1" from "installationsSelector"`
`And I additionally select "INST_0" from "installationsSelector"`
It just selects me the option INST_1 then it selects the INST_0, but the INST_1 is no longer selected.
http://screencast.com/t/FE49q23kr
I've tested with latest stable version of chrome
It's me again :) I'm not sure if this bug belongs to Mink, or if it's a limitation of Sahi itself.
Right now, it seems like Sahi doesn't save the session cookie during scenarios. Thus any tests that rely on cookies fail in Sahi (they work with Goutte though).
Is this a known limitation?
@everzet please
How to Subcontext MinkContext? Please an Example! I have tried for hours, beg you give me an example for:
Top Class is BehatContext and subcontext MinkContext like below (it is not working as it is)
public function __construct(array $parameters) {
//parent::__construct($this->getKernel());
$this->useContext('clicking', new ClickingContext($parameters));
$this->useContext('dropoff', new DropoffContext($parameters));
$this->useContext('mink', new MyMinkContext($this->getKernel()));
And for:
Top Class is MinkContext and subcontext other two BehatContext (this was my schema broke it completely)
public function __construct(HttpKernelInterface $kernel) {
$this->useContext('clicking', new ClickingContext($kernel));
$this->useContext('dropoff', new DropoffContext($kernel));
parent::__construct($kernel);
}
?
I could not do it banned image
I haven't pinpointed the issue yet (I believe it's something to do in click() in the else or in getField() when it makes the new Crawler), but pages that use a tag don't always work properly. I patched Crawler, Form and Link in DomCrawler a couple months ago to support this.
Before Mink came out, my raw Goutte steps worked properly with the base tag.
The problem is that sometimes the $base parameter isn't set in the form object, which causes incorrect behavior.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.