Giter Site home page Giter Site logo

microsoft / playwright-cli Goto Github PK

View Code? Open in Web Editor NEW
1.0K 23.0 45.0 1.11 MB

CLI for common Playwright actions. Record and generate Playwright code, inspect selectors and take screenshots.

Home Page: https://playwright.dev

License: Apache License 2.0

JavaScript 100.00%
playwright

playwright-cli's Introduction

🎭 Playwright CLI

Please use npx playwright

Playwright Command Line Interface has moved directly to Playwright.

$ npx playwright --help

Issues/features?

Please submit any issues or feature requests to the playwright repository and mention "cli" in the title.

playwright-cli's People

Contributors

arjunattam avatar aslushnikov avatar dependabot[bot] avatar dgozman avatar joeleinbinder avatar kblok avatar kumaraditya303 avatar microsoftopensource avatar mxschmitt avatar pavelfeldman avatar sonjaq avatar yury-s avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

playwright-cli's Issues

codegen: fails to capture certain websites

https://github.com

Launch playwright-cli record https://github.com and then clicking in the search in the top right corner, type "playwright" and hit "enter". Typing is missing.

const assert = require('assert');
const { chromium, firefox, webkit } = require('playwright');

(async() => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // Go to https://github.com/
  await page.goto('https://github.com/');

  // 0× click
  await page.click('li[id=jump-to-suggestion-search-global]');
  assert.equal(page.url(), 'https://github.com/search?q=playwright');
})();

https://playwright.dev

Launch playwright-cli record https://playwright.dev, click search and try typing evaluate there. The generated selector is broken

const assert = require('assert');
const { chromium, firefox, webkit } = require('playwright');

(async() => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // Go to https://playwright.dev/
  await page.goto('https://playwright.dev/');

  // Click <selector>
  await page.click('<selector>');

  // Fill <selector>
  await page.fill('<selector>', 'evaluate');
  assert.equal(page.url(), 'https://playwright.dev/#version=v1.2.0&path=docs%2Fapi.md&q=pageevaluatepagefunction-arg');
})();

https://theverge.com

  1. Launch playwright-cli record https://theverge.com, try clicking on any article. Generated selector is broken - it's simply img[alt=""].
const assert = require('assert');
const { chromium, firefox, webkit } = require('playwright');

(async() => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // Go to https://www.theverge.com/
  await page.goto('https://www.theverge.com/');

  // Click img[alt=""]
  await Promise.all([
    page.waitForNavigation({ url: 'https://www.theverge.com/2020/7/7/21314972/away-employees-ceo-steph-korey-resign-native-american-costume' }),
    page.click('img[alt=""]')
  ]);
})();
  1. Clicking on any menus on theVerge.com is not captured as well.

https://bbc.com

Launch playwright-cli record https://bbc.com, try clicking on any article. The generated selector has wrongly escaped quotes, and doesn't point to the element I clicked.

const assert = require('assert');
const { chromium, firefox, webkit } = require('playwright');

(async() => {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // Go to https://www.bbc.com/
  await page.goto('https://www.bbc.com/');

  // Click div[id=page] text=/\s*Brazil's Bolsonaro tests positive for Covid-19\s*/
  await page.click('div[id=page] text=/\\s*Brazil\\'s Bolsonaro tests positive for Covid-19\\s*/');
})();

Exception while generating theverge script

Execution Context is not available in detached frame "https://tpc.googlesyndication.com/sodar/sodar2/217/runner.html" (are you trying to evaluate?)
at Connection.sendMessageToServer (/Users/pfeldman/code/playwright-cli/node_modules/playwright/lib/client/connection.js:69:15)
at Proxy. (/Users/pfeldman/code/playwright-cli/node_modules/playwright/lib/client/channelOwner.js:54:53)
at Frame._extendInjectedScript (/Users/pfeldman/code/playwright-cli/node_modules/playwright/lib/client/frame.js:355:44)
at ScriptController._ensureInstalledInFrame (/Users/pfeldman/code/playwright-cli/lib/scriptController.js:38:30)
at Page. (/Users/pfeldman/code/playwright-cli/lib/scriptController.js:33:49)

selectors: ignore value property on input elements

Issue: Input element rendered by React which has the value rendered in it.

<input type="text" id="first_name" class="ant-input" value="Hi Mama">

Expected:

  await page.click('//input[@id="first_name"]');

  await page.fill('//input[@id="first_name"]', 'Admin1');

Actual:

  // Click //input[normalize-space(@type)='text' and normalize-space(@value)='Admin']
  await page.click('//input[normalize-space(@type)=\'text\' and normalize-space(@value)=\'Admin\']');

  // Fill //input[normalize-space(@type)='text' and normalize-space(@value)='Admin']
  await page.fill('//input[normalize-space(@type)=\'text\' and normalize-space(@value)=\'Admin\']', 'Admin1');

I would treat ID and name as static attributes which get prioritised in the selector engine.

Chrome has a feature integrated in the DevTools to copy a selector, maybe we can steal some logic from there!

image

Shadow DOM support

First of all congratulations on the release. I am super excited to start using this software.

Bug report:

  1. Selector generation fails to match the element in shadow DOM. It matches the closest parent that is not in shadow DOM.
  2. playwright.selector($0) for an element in shadow DOM returns error:
__playwright_evaluation_script__763:192 Uncaught TypeError: Cannot read property 'length' of undefined
    at Object.parseSelector (__playwright_evaluation_script__763:192)
    at InjectedScript.parseSelector (__playwright_evaluation_script__763:688)
    at Object.buildSelector (eval at extend ((index):733), <anonymous>:1032:43)
    at ConsoleAPI.buildSelector (eval at extend ((index):733), <anonymous>:351:36)
    at Object.selector (eval at extend ((index):733), <anonymous>:319:41)
    at <anonymous>:1:12
parseSelector @ __playwright_evaluation_script__763:192
parseSelector @ __playwright_evaluation_script__763:688
buildSelector @ VM1432:1032
buildSelector @ VM1432:351
selector @ VM1432:319
(anonymous) @ VM2126:1

Here is the site I was using to find these issues: fanteam.com

codegen: selector won't get updated after Tab press

<input id="1" placeholder="1" />
<input id="2" placeholder="2" />

Test case:

  • go into element #1
  • press tab
  • type something

Expected result:
Page fill with the #2 element

Actual result:
Page fill with the #1 element

Relates #21

Use codegen with existing browser and without incognito mode.

I'm in an enterprise environment, where I can't download arbitrary binaries. I have Chrome and Edgium installed on my machine, and I've been using them with puppeteer-core, providing path to executables. When I tried installing playwright-cli, I got a dns error for playwright.azureedge.net. I suspect this is due to my network restrictions. One way or another, my company firewalls won't let me download a custom browser.

I managed to install playwright-cli via npm install --ignore-scripts, but it looks like I can't provide a path to my existing browser executable. I got around this by copying whole chrome folder to the path playwright-cli expected to find it, launched npx playwright-cli codegen wikipedia.org and it worked, window showed up and actions were recorded in script. But resulting window is in incognito mode, so I can't access any of my intranet sites (all requiring certificate to authenticate).

I think this kind of restrictions will be typical for an enterprise environment, so would it be possible to use codegen with existing browser and without incognito mode?

codegen: popup referenced without being defined

Testing on online.visualstudio.com

  • Page opens a new tab
  • I click on the new page
  • codegen picks up the click in the new page, but does not generate waitForEvent
  // Click input[aria-label="Codespace Name"]
  await page.click('input[aria-label="Codespace Name"]');

  // Fill input[aria-label="Codespace Name"]
  await page.fill('input[aria-label="Codespace Name"]', 'asdf');

  // Click button[aria-label="Show pricing information..."]
  await page.click('button[aria-label="Show pricing information..."]'); <---- This opens a new tab

  // Click text=/.*Try for free.*/
  await popup1.click('text=/.*Try for free.*/');
  // assert.equal(popup1.url(), 'https://azure.microsoft.com/en-us/free/');

PDF local HTML files issue

First of all, great work with playwright-cli! 🚀

I've tried using playwright-cli for the local HTML file but got an issue, eg.

npx playwright-cli pdf ./my-dir/index.html my-file.pdf

Any chance to fix the above one? Thank you in advance! 🍻

How to properly output the generated code to a file?

My current output looks something like..

[38;5;205mconst�[0m { chromium } = �[38;5;220mrequire�[0m(�[38;5;130m'playwright'�[0m);

(�[38;5;205masync�[0m () => {
  �[38;5;205mconst�[0m browser = �[38;5;205mawait�[0m chromium.launch({
    headless: �[38;5;159mfalse�[0m
  });
  �[38;5;205mconst�[0m context = �[38;5;205mawait�[0m browser.newContext();
})();
�[1A�[2K
  �[38;5;23m// Open new page�[0m
  �[38;5;205mconst�[0m page = �[38;5;205mawait�[0m context.newPage();
})();

Typo in CLI PDF examples

There is a typo in the playwright-cli pdf command example. PR incoming.

-      console.log('  $ pdf https://example.com example.png');
+      console.log('  $ pdf https://example.com example.pdf');

codegen: created popup results in ReferenceError: Cannot access 'page1' before initialization

Command: node lib/cli.js codegen file://$(pwd)/test.html

test.html:
<a href="https://instagram.com/microsoft" target="_blank" rel="noopener noreferrer">Instagram</a>

Actual result:

  // Click text="Instagram"
  const [page1] = await Promise.all([
    page1.waitForEvent('popup'),
    page1.waitForNavigation(/*{ url: 'https://www.instagram.com/microsoft/' }*/),
    page1.click('text="Instagram"')
  ]);

Expected result (I guess):

  const [page1] = await Promise.all([
    page.waitForEvent('popup'),
    page.click('text="Instagram"')
  ]);

No output support for codegen command

I saw in the master source that the command codegen should support a output option.
If I try that inside the command line I only get a error message.

Master source code

program
    .command('codegen [url]')
    .description('open page and generate code for user actions')
    .option('-o --output <file name>', 'saves the generated script to a file')
    .action(function(url, command) {
      codegen(command.parent, url, command.output);
    }).on('--help', function() {
      console.log('');
      console.log('Examples:');
      console.log('');
      console.log('  $ record');
      console.log('  $ -b webkit record https://example.com');
    });
playwright-cli codegen -o login.js localhost:8090
error: unknown option '-o'

playwright-cli -V
Version 0.5.2

codegen: Weird behaviour on google.com

Steps to reproduce:

  • Open incognito tab
  • Navigate to https://www.google.com/
  • Click I agree

image

Expected result: Stay on google.com
Actual result: Redirected to https://www.google.com/doodles/

codegen: CMD + F won't work

node lib/cli.js codegen https://ant.design/components/select/

Searching through the page won't work. The Chromium search popup won't be shown. Seems like we prevent the keyboard events somehow.

Expected:
image

Actual:
Nothing

device emulation: Firefox does not support isMobile

npx playwright-cli --device "JioPhone 2" codegen wikipedia.org

Results in:

(node:95076) UnhandledPromiseRejectionWarning: browser.newContext: options.isMobile is not supported in Firefox
    at FirefoxBrowser._wrapApiCall (/Users/max/Development/playwright-cli/node_modules/playwright/lib/client/channelOwner.js:76:15)
    at FirefoxBrowser.newContext (/Users/max/Development/playwright-cli/node_modules/playwright/lib/client/browser.js:42:21)
    at launchContext (/Users/max/Development/playwright-cli/lib/cli.js:169:35)
    at async open (/Users/max/Development/playwright-cli/lib/cli.js:197:69)
(node:95076) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:95076) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Reported by Arjun, needs to be clarified how we should handle that.

traceviewer preview

How can I preview traceviewer feature?
When I run cli,I can not find it from dev tools

codegen: popups could be handled better

When clicking "Select your room" on booking.com, I get the following:

  await page.click('//div[normalize-space(@aria-label)=\'Search results\' and normalize-space(@role)=\'region\']/div[4]/div[2]/div[3]/div/div[normalize-space(@role)=\'presentation\']/div/div/div[2]/div[3]/div/div/div/a[normalize-space(.)=\'Select your room\']');

  await popup1.goto('https://www.booking.com/searchresults.html?long-long-long-long-url-here');

Perhaps, we can use page.waitForEvent('popup') to introduce popup1? Also, it is loading the initial url by itself, can probably figure that out as well.

codgen: textarea input event won't get captured

Command: node lib/cli.js codegen file://$(pwd)/test.html

test.html:
<textarea data-testid="foobar"></textarea>

Actual result:

Nothing

Expected result:

  await page.fill('textarea[data-testid="foobar"]', 'foobar123');

codegen: Ant Design select will instantly hide

The gif doesn't look very good. The issue is that the dropdown will instantly disappear when we click on it using the playwright-cli.

Screen-Recording-2020-09-09-at-13 48 28

node lib/cli.js codegen https://ant.design/components/select/

(scroll down until Option Group)

codegen: use device descriptors

npx playwright-cli --device="Pixel 2" codegen wikipedia.org

Currently, this is generated as

(async() => {
  const browser = await chromium.launch({
    headless: false
  });
  const context = await browser.newContext({
    userAgent: 'Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36',
    viewport: {
      width: 411,
      height: 731
    },
    isMobile: true,
    hasTouch: true,
    defaultBrowserType: 'chromium'
  });

It would be great to see this as

(async() => {
  const browser = await chromium.launch({
    headless: false
  });
  const context = await browser.newContext({
   ...devices['Pixel 2']
  });

open: manually quitting from mac webkit produces an error

When I use "Quit Playwright" menu item, cli terminates with an error:

(node:10230) UnhandledPromiseRejectionWarning: browser.close: Target browser or context has been closed
at WebKitBrowser._wrapApiCall (/Users/dgozman/code/pwuse/node_modules/playwright-cli/node_modules/playwright/lib/client/channelOwner.js:76:15)
at WebKitBrowser.close (/Users/dgozman/code/pwuse/node_modules/playwright-cli/node_modules/playwright/lib/client/browser.js:74:21)
at Page. (/Users/dgozman/code/pwuse/node_modules/playwright-cli/lib/cli.js:160:25)
at Page.emit (events.js:210:5)
at Page._onClose (/Users/dgozman/code/pwuse/node_modules/playwright-cli/node_modules/playwright/lib/client/page.js:138:14)
at Proxy. (/Users/dgozman/code/pwuse/node_modules/playwright-cli/node_modules/playwright/lib/client/page.js:66:46)
at Proxy.emit (events.js:210:5)
at Connection.dispatch (/Users/dgozman/code/pwuse/node_modules/playwright-cli/node_modules/playwright/lib/client/connection.js:99:25)
at Immediate._onImmediate (/Users/dgozman/code/pwuse/node_modules/playwright-cli/node_modules/playwright/lib/inprocess.js:37:85)
at processImmediate (internal/timers.js:439:21)
(node:10230) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:10230) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

[codegen/python]: browser.close() merged with comment

Run npx playwright-cli@next codegen --target python, do some actions and close the browser.

    # Close page
    await page.close()
    # Close browser    await browser.close()
asyncio.get_event_loop().run_until_complete(main())

We could add line breaks before browser.close and also before the last line for readability.

[QUESTION] recorder click isn't re-runnable; how to convert the click to playwright syntax? Please Help

Need some help please.

I have a company internal website with Power BI visuals. I am trying to use playwright to develop test cases that would test the reports like a user would manipulate them. I am having trouble finding what is the correct object to use to click on a row in a visual. Clicking this element, would adjust all of the other visuals. I would also need to "get" this element text.

I used a public Power BI report that has the same class names and object names like (pivotTableCellWrap cell-interactive tablixAlignLeft )to use as a test for my question since I can not provide a link to our reports.

I ran the following.
npx playwright-cli codegen https://community.powerbi.com/t5/Data-Stories-Gallery/Customer-Analysis-Dashboard/td-p/630893

const { chromium } = require('playwright');

(async () => {
const browser = await chromium.launch({
headless: false
});
const context = await browser.newContext();

// Open new page
const page = await context.newPage();

// Go to https://community.powerbi.com/t5/Data-Stories-Gallery/Customer-Analysis-Dashboard/td-p/630893
await page.goto('https://community.powerbi.com/t5/Data-Stories-Gallery/Customer-Analysis-Dashboard/td-p/630893');

// Click text="Weimei Corp"
await page.frame({
url: 'https://app.powerbi.com/view?r=eyJrIjoiNDIyYWJjM2ItYTgwMi00NjQzLWEzYTItZWMwNzIzOTY2MDUxIiwidCI6IjA0ZWM2MTA5LTRjNzktNGM3My1hZTcxLWE0NzRjMDlhMWY1YSJ9'
}).click('text="Weimei Corp"');

// Close page
await page.close();

The play back actually works, for this site but not mine, because my isn't including the "view" url in the page frame. Maybe because this report was setup specifically to be public and they had to adjust how they did it.

when I do my recording I get the following which doesn't work on playback.

// 0× click
await page.click('//iframe');

// Click text="THERESA"
await page.click('text="THERESA"');

This is what I am doing to get the frame name, not sure where the //iframe came from and it doesn't work either way.
//locate the iframe name visual sandbox
const frame = page.frames().find(frame => frame.name() === 'visual-sandbox');
console.log(frame.name());

I also noticed that during the recording for a different text than I saw a difference reference and that is because that name is in there twice but not as a duplicate just different info for it. I tried that syntax and it didn't work.
//div[3][normalize-space(.)='THERESA WADE' and normalize-space(@title)='THERESA WADE']"

I have attached a few screen shots from my visual. ( I apologize but I had to blur out sensitive PI data). There are some that shows what is displayed when I hover over the different sections when I am using the recording. Which is completely different than what I get from Chrome Dev Tools and do copy selector, js path, or element.

How can I convert my recordings to the correct object and syntax.
Which object and method is the correct one for clicking in pivotTableCellWrap cell-interactive tablixAlignLeft ? then also get that value for sanity check?

Here are the element references:
Copy Element

THERESA WADE

COPY SELECTOR
#pvExplorationHost > div > div > exploration > div > explore-canvas-modern > div > div.canvasFlexBox > div > div.displayArea.disableAnimations.fitToPage > div.visualContainerHost > visual-container-repeat > visual-container-modern:nth-child(3) > transform > div > div:nth-child(4) > div > visual-modern > div > div > div.tableEx > div.innerContainer > div.bodyCells > div > div > div:nth-child(1) > div:nth-child(1)

COPY JSPATH
document.querySelector("#pvExplorationHost > div > div > exploration > div > explore-canvas-modern > div > div.canvasFlexBox > div > div.displayArea.disableAnimations.fitToPage > div.visualContainerHost > visual-container-repeat > visual-container-modern:nth-child(3) > transform > div > div:nth-child(4) > div > visual-modern > div > div > div.tableEx > div.innerContainer > div.bodyCells > div > div > div:nth-child(1) > div:nth-child(1)")

COPY XPATH
//*[@id="pvExplorationHost"]/div/div/exploration/div/explore-canvas-modern/div/div[2]/div/div[2]/div[2]/visual-container-repeat/visual-container-modern[3]/transform/div/div[3]/div/visual-modern/div/div/div[2]/div[1]/div[4]/div/div/div[1]/div[1]

screenshots from my recording.

capturedifferentreference
clickonRowwithNameX
divreference

codegen: fill does not capture full text string

Specific to bing.com

Repro steps:

  • go to bing.com
  • click on the search input field
  • type "git" quickly
  • type "hub" after a short pause
  • the codegen shows only git (await page.fill('input[aria-label="Enter your search term"]', 'git');
    • it seems bing.com tries to autocomplete the field, which does not play well with codegen

codegen: down arrow key press not recorded

Steps to repro:

  1. Run codegen on wikipedia.org
npx playwright-cli codegen wikipedia.org
  1. Click on the search box and start typing. This will be recorded correctly as
await page.fill('input[name="search"]', 'pl');
  1. Press down arrow to highlight auto-complete suggestions of the search box. This key press is not recorded.

libenchant.so.1: cannot open shared object file: No such file or directory

Ubuntu 20.04

Tried running
npx playwright-cli --device="iPhone 11" open wikipedia.org

As per Readme.

Result:
[err] /home/dan/.cache/ms-playwright/webkit-1343/minibrowser-gtk/bin/MiniBrowser: error while loading shared libraries: libenchant.so.1: cannot open shared object file: No such file or directory

codegen: multiple click actions recorded

Testing on online.visualstudio.com

  • Click on an element
  • Expect element click code snippet to be generated once
  • codegen generates multiple clicks - feels like playwright retry is showing up as generated code
  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

  // Click text="Dotfiles (Optional)"
  await page.click('text="Dotfiles (Optional)"');

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.